<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>The Corner Datastore</title>
    <description>Varun&apos;s blog. A place for blogging with blogs.
</description>
    <link>https://varunmehta.com/</link>
    <atom:link href="https://varunmehta.com/feed.xml" rel="self" type="application/rss+xml"/>
    <pubDate>Tue, 21 Apr 2026 23:44:36 -0400</pubDate>
    <lastBuildDate>Tue, 21 Apr 2026 23:44:36 -0400</lastBuildDate>
    <generator>Jekyll v4.3.3</generator>
    
      <item>
        <title>Vibe Coding: How I found a data disclosure error by checking more than just API endpoints</title>
        <description>&lt;p&gt;There &lt;em&gt;is&lt;/em&gt; promise in what modern-style AI code tools can do to make life easier for engineers and other technically-minded people as a companion to their knowledge and experience.&lt;/p&gt;

&lt;h4 id=&quot;the-takeaway&quot;&gt;The Takeaway&lt;/h4&gt;

&lt;p&gt;It also can work well for vibe coding a proof of concept to explore ideas. If you’re non-technical and vibe coding some software, &lt;em&gt;please&lt;/em&gt; ask at least &lt;em&gt;one&lt;/em&gt; technical friend to take a look, even casually, at what code was generated or the final website/app.&lt;/p&gt;

&lt;h4 id=&quot;disclaimers&quot;&gt;Disclaimers&lt;/h4&gt;

&lt;ul&gt;
  &lt;li&gt;I won’t share who the founder or the startup is; it isn’t relevant.&lt;/li&gt;
  &lt;li&gt;No live end-consumer data was disclosed.&lt;/li&gt;
  &lt;li&gt;This is meant as an illustrative example, not the one specific thing you need to watch out for. Don’t just tell ChatGPT to check for this vulnerability and think you’re 100% safe.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&quot;context&quot;&gt;Context&lt;/h4&gt;

&lt;p&gt;Knowing the promise of modern AI agents in making it easier to write code, I’ve been curious to see how people are using it. Engineers on my team have liked using it as a companion, filling skills gaps to help keep the work moving. I’ve been exploring it for certain development work, too. But what are startup founders doing? In particular, what are non-technical founders doing?&lt;/p&gt;

&lt;p&gt;Non-technical founders are doing more vibe coding, and I wanted to know what kind of code is getting deployed, potentially into production, and potentially in more secure data contexts.&lt;/p&gt;

&lt;h4 id=&quot;the-target&quot;&gt;The Target&lt;/h4&gt;

&lt;p&gt;The target is a B2B2C web application that processes and stores PII, and also provides a front-end for authenticated business accounts to view a scoped set of that PII. It includes one demo account which has access to demo data for one business. Other than the demo business account, I did not get access to the codebase, application db, or app backend. Only the knowledge that it was vibecoded and the interface that a regular user would use. I’d call it “informal black box testing” to explore how LLM-generated systems work, not explicitly to find any problems.&lt;/p&gt;

&lt;h4 id=&quot;the-method&quot;&gt;The Method&lt;/h4&gt;

&lt;p&gt;Keeping this brief, rather than telling a whole story.&lt;/p&gt;

&lt;p&gt;I loaded the public-facing website, and found that it makes a series of requests to load and then display user data for the demo business account. The request sequence looks like:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GET index&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GET companies?select=*&amp;amp;id=eq.demo_guid&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GET users?select=*,user_contact:user_contacts(email,phone_number, zip_code,birth_year,name) &amp;amp;company_id=eq.demo_guid&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GET companies?select=id,demo_enabled&amp;amp;slug=eq.company_demo_name&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GET companies?select=*&amp;amp;id=eq.company_demo_guid&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GET users?select=*,user_contact:user_contacts(email,phone_number, zip_code,birth_year,name) &amp;amp;company_id=eq.company_demo_guid&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GET users?select=*,user_contact:user_contacts(email,phone_number, zip_code,birth_year,name) &amp;amp;company_id=eq.company_demo_guid&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GET companies?select=*&amp;amp;id=eq.company_demo_guid&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Some funny notes:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;The page fetches the data for a ‘demo’ and immediately discards it. This was probably part of some early demo/proof of concept work, before support was added for multiple companies, with each company have a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;demo_enabled&lt;/code&gt; state.&lt;/li&gt;
  &lt;li&gt;The page fetches information about the demo company multiple times (5, 8), and also fetches information about all users multiple times (6, 7). It is not clear why.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;More important notes:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Requests all use &lt;a href=&quot;https://docs.postgrest.org/en/v14/&quot;&gt;PostgREST&lt;/a&gt; with the full query being made by the web front-end itself.
    &lt;ul&gt;
      &lt;li&gt;The table name is the URL base (users, companies, etc.).&lt;/li&gt;
      &lt;li&gt;The where clauses are each URL parameter after the select (e.g. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;company_id=eq.company_demo_guid&lt;/code&gt;).&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;All the data being returned is highly-structured JSON&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;There’s a DB-like interface exposed on to front end, and the API endpoints seemed to have a &lt;em&gt;little&lt;/em&gt; security I had to workaround.&lt;/strong&gt; This is the easiest and first thing to check. Initially I tried using Firefox’s ‘Edit and Resend’ feature to alter the requests, to change the parameters but making requests out of order was yielding strange responses: Firefox would say 3.34kB transferred (0B in size). Only requests made in the expected order would provide results.&lt;/p&gt;

&lt;p&gt;Rather than trying to modify the javascript powering the front-end, I used the free and open source &lt;a href=&quot;https://requestly.com/&quot;&gt;Requestly&lt;/a&gt; extension to rewrite the requests on the fly. Set up multiple rules to intercept those calls to change the parameters.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A little back-end validation to work around: limit 1.&lt;/strong&gt;  There was some back-end validation to limit certain queries to only return one result, and the server returns an HTTP error with information about that limit. This meant I couldn’t get a full list of every company in one query. I just worked around this by adding an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;offset=1&lt;/code&gt; parameter to 
the PostgREST query, and incremented the offset with every call. If there were more than dozens of customers, I would have automated or found a faster way to pull this information.&lt;/p&gt;

&lt;p&gt;This method allowed me to modify queries to return arbitrary data (with a different/no &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;where&lt;/code&gt; clause), and to return the count on any table in the db, and retrieve rows, either in bulk or one at a time.&lt;/p&gt;

&lt;h4 id=&quot;the-findings&quot;&gt;The Findings&lt;/h4&gt;

&lt;p&gt;This simple method in this case was enough, without ever having seen a line of the app code, to:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Get a full list of all companies, whether they had a demo mode or were in production.&lt;/li&gt;
  &lt;li&gt;Get a listing of all individual users that shared their information with this app, including their names, emails, locations, and highly-personal details.&lt;/li&gt;
  &lt;li&gt;Get data from any other table in the database available to the db user – I didn’t find anything too interesting though. At least not as interesting as the two previous bullet points.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Other than that I also found:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;A semi-functional admin user signup/account creation page&lt;/li&gt;
  &lt;li&gt;A password reset function&lt;/li&gt;
  &lt;li&gt;A number of redundant calls and data processing functions, probably abandoned by the LLM while iteratively building the codebase.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&quot;the-follow-ups-and-my-recommendations&quot;&gt;The Follow-ups and My Recommendations&lt;/h4&gt;

&lt;p&gt;I did connect with my friend to let them know what I found, and how easy it was. No code-level exploits involved. Minimal technical knowledge. No knowledge of how the back-end was implemented. And thanked them for the opportunity to learn a lot in the process. Thankfully they hadn’t gone live with real people’s data yet, and I offered to take a look before they do, as a friendly courtesy. They’ll take me up on that offer :-)&lt;/p&gt;

&lt;p&gt;What should you do? At minimum, get a friend with technical knowledge, someone you trust, to take a look before you launch/publish/go to production. Ideally leverage the LLM as a way to learn more yourself about what your codebase is and what it does. If you’re staking your reputation and your company on it, &lt;em&gt;someone&lt;/em&gt;, at &lt;em&gt;some level&lt;/em&gt; should understand what it is and what it does.&lt;/p&gt;

&lt;p&gt;— Varun&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Who’s Varun? I’ve been in Product at companies like at &lt;a href=&quot;https://clearme.com&quot;&gt;CLEAR&lt;/a&gt; and &lt;a href=&quot;https://noom.com&quot;&gt;Noom&lt;/a&gt;, Hotel Tech with ALICE (acquired by &lt;a href=&quot;https://actabl.com/&quot;&gt;Actabl&lt;/a&gt;), and I was previously founder of an HR tech startup &lt;a href=&quot;http://disqovery.com&quot;&gt;Disqovery&lt;/a&gt;. I have worn many hats, and I like making things. Advising startups and early stage founders. I also like talking business. You can reach me at &lt;a href=&quot;mailto:me@varunmehta.com&quot;&gt;me@varunmehta.com&lt;/a&gt;, &lt;a href=&quot;https://fosstodon.org/@smartperson&quot;&gt;Mastodon&lt;/a&gt;, &lt;a href=&quot;https://github.com/smartperson&quot;&gt;Github&lt;/a&gt;, and &lt;a href=&quot;https://linkedin.com/in/varunkmehta&quot;&gt;LinkedIn&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
</description>
        <pubDate>Sat, 18 Apr 2026 00:00:00 -0400</pubDate>
        <link>https://varunmehta.com/technology/2026/04/18/vibe-code.html</link>
        <guid isPermaLink="true">https://varunmehta.com/technology/2026/04/18/vibe-code.html</guid>
        
        
        <category>technology</category>
        
      </item>
    
      <item>
        <title>How to repair a broken Force Sensitive Resistor (FSR) in a GalaxiPad DDR Dance Pad</title>
        <description>&lt;p&gt;I still enjoy Dance Dance Revolution (DDR), but (good) hardware is still hard to find. Ordered a &lt;a href=&quot;https://www.galaxidance.com/&quot;&gt;Galaxi Dance Pad&lt;/a&gt;. Unfortunately, it broke after a few days of use – and the guy that runs the company has not been responsive. One of the arrows stopped responding at all.&lt;/p&gt;

&lt;p&gt;There was no obvious disconnect with the wiring inside. On a recommendation, I purchased &lt;a href=&quot;https://buyinterlinkelectronics.com/collections/x-ux-force-sensors/products/fsr-ux-408-300mm-length&quot;&gt;this replacement&lt;/a&gt; FSR strip, placed it between the aluminum bar and rubber strip, and then had to figure out how to securely connect the metal pins to the rest of the wiring for the system.&lt;/p&gt;

&lt;div class=&quot;mdl-grid&quot;&gt;

  
  &lt;div class=&quot;mdl-cell mdl-cell--4-col&quot;&gt;
  
    
    &lt;a href=&quot;/img/2026/02/1-pins.jpg&quot; data-lightbox=&quot;galaxi&quot; title=&quot;pins the FSR comes with&quot;&gt;
      
        &lt;img src=&quot;https://res.cloudinary.com/varun-blog/image/fetch/c_limit,f_auto,q_auto,w_800/https://varunmehta.com/img/2026/02/1-pins.jpg&quot; srcset=&quot;https://res.cloudinary.com/varun-blog/image/fetch/c_limit,f_auto,q_auto,w_320/https://varunmehta.com/img/2026/02/1-pins.jpg 320w,
https://res.cloudinary.com/varun-blog/image/fetch/c_limit,f_auto,q_auto,w_640/https://varunmehta.com/img/2026/02/1-pins.jpg 640w,
https://res.cloudinary.com/varun-blog/image/fetch/c_limit,f_auto,q_auto,w_960/https://varunmehta.com/img/2026/02/1-pins.jpg 960w,
https://res.cloudinary.com/varun-blog/image/fetch/c_limit,f_auto,q_auto,w_1280/https://varunmehta.com/img/2026/02/1-pins.jpg 1280w,
https://res.cloudinary.com/varun-blog/image/fetch/c_limit,f_auto,q_auto,w_1600/https://varunmehta.com/img/2026/02/1-pins.jpg 1600w&quot; sizes=&quot;(min-width: 50rem) 50rem, 90&quot; width=&quot;4032&quot; height=&quot;3024&quot; crossorigin=&quot;anonymous&quot; /&gt;
      
    &lt;/a&gt;
  &lt;/div&gt;

  
  &lt;div class=&quot;mdl-cell mdl-cell--4-col&quot;&gt;
  
    
    &lt;a href=&quot;/img/2026/02/2-socket.jpg&quot; data-lightbox=&quot;galaxi&quot; title=&quot;IC socket fits nicely onto the pins&quot;&gt;
      
        &lt;img src=&quot;https://res.cloudinary.com/varun-blog/image/fetch/c_limit,f_auto,q_auto,w_800/https://varunmehta.com/img/2026/02/2-socket.jpg&quot; srcset=&quot;https://res.cloudinary.com/varun-blog/image/fetch/c_limit,f_auto,q_auto,w_320/https://varunmehta.com/img/2026/02/2-socket.jpg 320w,
https://res.cloudinary.com/varun-blog/image/fetch/c_limit,f_auto,q_auto,w_640/https://varunmehta.com/img/2026/02/2-socket.jpg 640w,
https://res.cloudinary.com/varun-blog/image/fetch/c_limit,f_auto,q_auto,w_960/https://varunmehta.com/img/2026/02/2-socket.jpg 960w,
https://res.cloudinary.com/varun-blog/image/fetch/c_limit,f_auto,q_auto,w_1280/https://varunmehta.com/img/2026/02/2-socket.jpg 1280w,
https://res.cloudinary.com/varun-blog/image/fetch/c_limit,f_auto,q_auto,w_1600/https://varunmehta.com/img/2026/02/2-socket.jpg 1600w&quot; sizes=&quot;(min-width: 50rem) 50rem, 90&quot; width=&quot;4032&quot; height=&quot;3024&quot; crossorigin=&quot;anonymous&quot; /&gt;
      
    &lt;/a&gt;
  &lt;/div&gt;

  
  &lt;div class=&quot;mdl-cell mdl-cell--4-col&quot;&gt;
  
    
    &lt;a href=&quot;/img/2026/02/3-solder.jpg&quot; data-lightbox=&quot;galaxi&quot; title=&quot;solder wires to the pin&quot;&gt;
      
        &lt;img src=&quot;https://res.cloudinary.com/varun-blog/image/fetch/c_limit,f_auto,q_auto,w_800/https://varunmehta.com/img/2026/02/3-solder.jpg&quot; srcset=&quot;https://res.cloudinary.com/varun-blog/image/fetch/c_limit,f_auto,q_auto,w_320/https://varunmehta.com/img/2026/02/3-solder.jpg 320w,
https://res.cloudinary.com/varun-blog/image/fetch/c_limit,f_auto,q_auto,w_640/https://varunmehta.com/img/2026/02/3-solder.jpg 640w,
https://res.cloudinary.com/varun-blog/image/fetch/c_limit,f_auto,q_auto,w_960/https://varunmehta.com/img/2026/02/3-solder.jpg 960w,
https://res.cloudinary.com/varun-blog/image/fetch/c_limit,f_auto,q_auto,w_1280/https://varunmehta.com/img/2026/02/3-solder.jpg 1280w,
https://res.cloudinary.com/varun-blog/image/fetch/c_limit,f_auto,q_auto,w_1600/https://varunmehta.com/img/2026/02/3-solder.jpg 1600w&quot; sizes=&quot;(min-width: 50rem) 50rem, 90&quot; width=&quot;4032&quot; height=&quot;3024&quot; crossorigin=&quot;anonymous&quot; /&gt;
      
    &lt;/a&gt;
  &lt;/div&gt;

  
  &lt;div class=&quot;mdl-cell mdl-cell--4-col&quot;&gt;
  
    
    &lt;a href=&quot;/img/2026/02/4-assembled.jpg&quot; data-lightbox=&quot;galaxi&quot; title=&quot;tape it all together (later used hot glue)&quot;&gt;
      
        &lt;img src=&quot;https://res.cloudinary.com/varun-blog/image/fetch/c_limit,f_auto,q_auto,w_800/https://varunmehta.com/img/2026/02/4-assembled.jpg&quot; srcset=&quot;https://res.cloudinary.com/varun-blog/image/fetch/c_limit,f_auto,q_auto,w_320/https://varunmehta.com/img/2026/02/4-assembled.jpg 320w,
https://res.cloudinary.com/varun-blog/image/fetch/c_limit,f_auto,q_auto,w_640/https://varunmehta.com/img/2026/02/4-assembled.jpg 640w,
https://res.cloudinary.com/varun-blog/image/fetch/c_limit,f_auto,q_auto,w_960/https://varunmehta.com/img/2026/02/4-assembled.jpg 960w,
https://res.cloudinary.com/varun-blog/image/fetch/c_limit,f_auto,q_auto,w_1280/https://varunmehta.com/img/2026/02/4-assembled.jpg 1280w,
https://res.cloudinary.com/varun-blog/image/fetch/c_limit,f_auto,q_auto,w_1600/https://varunmehta.com/img/2026/02/4-assembled.jpg 1600w&quot; sizes=&quot;(min-width: 50rem) 50rem, 90&quot; width=&quot;3024&quot; height=&quot;4032&quot; crossorigin=&quot;anonymous&quot; /&gt;
      
    &lt;/a&gt;
  &lt;/div&gt;

&lt;/div&gt;

&lt;p&gt;It originally seems like they soldered the wires directly to the FSR pins, and covered the whole thing in hot glue. Instead, I found that a double wipe 0.1” (2.54mm) IC socket fits the pins from this strip perfectly. I soldered my own wires to the pins on the socket, plugged the sensor pins into the socket, and then covered the whole thing in tape. Ultimately the tape didn’t hold, and to make it a lasting repair I still covered the whole thing in hot glue. It’s been working great since.&lt;/p&gt;
</description>
        <pubDate>Sat, 14 Feb 2026 00:00:00 -0500</pubDate>
        <link>https://varunmehta.com/technology/pc/2026/02/14/repair-broken-fsr-galaxi-dance-pad.html</link>
        <guid isPermaLink="true">https://varunmehta.com/technology/pc/2026/02/14/repair-broken-fsr-galaxi-dance-pad.html</guid>
        
        
        <category>technology</category>
        
        <category>pc</category>
        
      </item>
    
      <item>
        <title>Notice: Modern PC BIOSes will ignore small EFI System Partitions (ESP)</title>
        <description>&lt;p&gt;Small note, but important:&lt;/p&gt;

&lt;p&gt;If you are reformatting or repartitioning disks, and using EFI/UEFI, your EFI System Partition (ESP) &lt;em&gt;must&lt;/em&gt; be probably at least 100MB or 1GB. If it’s smaller than that, your BIOS will silently ignore the partition and refuse to boot.&lt;/p&gt;

&lt;p&gt;No disk partitioning utility, boot repair utility, or bios screen warns you about this requirement. It doesn’t matter if your ESP is easily large enough to fit all of the files you need to fit.&lt;/p&gt;

&lt;p&gt;Posting this on the personal blog so hopefully it won’t disappear off the internet.&lt;/p&gt;
</description>
        <pubDate>Sat, 14 Feb 2026 00:00:00 -0500</pubDate>
        <link>https://varunmehta.com/technology/pc/2026/02/14/modern-bioses-ignore-small-esp.html</link>
        <guid isPermaLink="true">https://varunmehta.com/technology/pc/2026/02/14/modern-bioses-ignore-small-esp.html</guid>
        
        
        <category>technology</category>
        
        <category>pc</category>
        
      </item>
    
      <item>
        <title>Could the Sochting Oxydator Actually Work? I think so.</title>
        <description>&lt;p&gt;&lt;strong&gt;This is mostly copied from a YouTube comment I posted a few weeks ago. Brought onto the blog because Google can be capricious, and to improve discoverability.&lt;/strong&gt; There are a bunch of claims out there about the Söchting Oxydator, about how it can boost oxygen levels, clarify aquarium water, and keep your fish healthier.&lt;/p&gt;

&lt;p&gt;But is it even &lt;em&gt;possible&lt;/em&gt; for it to live up to the hype? Based on my quick back-of-the-envelope calculations, I think so.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;To be clear, I am not a chemist, just a general nerd. Someone more knowledgeable could and should think through this.&lt;/strong&gt;&lt;/p&gt;

&lt;h4 id=&quot;what-is-the-söchting-oxydator&quot;&gt;What is The Söchting Oxydator?&lt;/h4&gt;

&lt;div class=&quot;mdl-grid&quot;&gt;

  
  &lt;div class=&quot;mdl-cell mdl-cell--4-col&quot;&gt;
  
    
    &lt;a href=&quot;/img/2025/06/sochting-oxydator-w-bubbles.jpg&quot; data-lightbox=&quot;sochting&quot; title=&quot;Sochting Oxydator W with tiny bubbles https://www.a-koi.at/beluftung/oxydator/sochting-oxydator-w&quot;&gt;
      
        &lt;img src=&quot;https://res.cloudinary.com/varun-blog/image/fetch/c_limit,f_auto,q_auto,w_800/https://varunmehta.com/img/2025/06/sochting-oxydator-w-bubbles.jpg&quot; srcset=&quot;https://res.cloudinary.com/varun-blog/image/fetch/c_limit,f_auto,q_auto,w_320/https://varunmehta.com/img/2025/06/sochting-oxydator-w-bubbles.jpg 320w,
https://res.cloudinary.com/varun-blog/image/fetch/c_limit,f_auto,q_auto,w_600/https://varunmehta.com/img/2025/06/sochting-oxydator-w-bubbles.jpg 600w&quot; sizes=&quot;(min-width: 50rem) 50rem, 90&quot; width=&quot;600&quot; height=&quot;661&quot; crossorigin=&quot;anonymous&quot; /&gt;
      
    &lt;/a&gt;
  &lt;/div&gt;

  
  &lt;div class=&quot;mdl-cell mdl-cell--4-col&quot;&gt;
  
    
    &lt;a href=&quot;/img/2025/06/sochting-oxydator-yt-thumbnail.jpg&quot; data-lightbox=&quot;sochting&quot; title=&quot;Thumbnail showing a small Sochting Oxydator YT: Shrimps &amp;amp; Snails: https://youtu.be/qVg-P1mZ5jg&quot;&gt;
      
        &lt;img src=&quot;https://res.cloudinary.com/varun-blog/image/fetch/c_limit,f_auto,q_auto,w_800/https://varunmehta.com/img/2025/06/sochting-oxydator-yt-thumbnail.jpg&quot; srcset=&quot;https://res.cloudinary.com/varun-blog/image/fetch/c_limit,f_auto,q_auto,w_320/https://varunmehta.com/img/2025/06/sochting-oxydator-yt-thumbnail.jpg 320w,
https://res.cloudinary.com/varun-blog/image/fetch/c_limit,f_auto,q_auto,w_640/https://varunmehta.com/img/2025/06/sochting-oxydator-yt-thumbnail.jpg 640w,
https://res.cloudinary.com/varun-blog/image/fetch/c_limit,f_auto,q_auto,w_960/https://varunmehta.com/img/2025/06/sochting-oxydator-yt-thumbnail.jpg 960w,
https://res.cloudinary.com/varun-blog/image/fetch/c_limit,f_auto,q_auto,w_1280/https://varunmehta.com/img/2025/06/sochting-oxydator-yt-thumbnail.jpg 1280w,
https://res.cloudinary.com/varun-blog/image/fetch/c_limit,f_auto,q_auto,w_1280/https://varunmehta.com/img/2025/06/sochting-oxydator-yt-thumbnail.jpg 1280w&quot; sizes=&quot;(min-width: 50rem) 50rem, 90&quot; width=&quot;1280&quot; height=&quot;720&quot; crossorigin=&quot;anonymous&quot; /&gt;
      
    &lt;/a&gt;
  &lt;/div&gt;

&lt;/div&gt;

&lt;p&gt;&lt;a href=&quot;https://www.theshrimptank.com/hardware/sochting-oxydator/&quot;&gt;The Sochting Oxydator&lt;/a&gt; is still a mystery to me; there are a lot of pseudoscience-y claims out there. Basically, it’s a ceramic vessel that you fill with hydrogen peroxide solution, and it slowly reacts or releases the peroxide into the aquarium. There are a &lt;a href=&quot;https://sevenports.com/2022/02/15/peroxide-treatment-for-algae-in-aquariums/&quot;&gt;bunch&lt;/a&gt; &lt;a href=&quot;https://ouraquariums.com/hydrogen-peroxide-aquarium-dosage/&quot;&gt;of&lt;/a&gt; &lt;a href=&quot;https://theaquariumexpert.com/how-much-hydrogen-peroxide-in-aquarium/&quot;&gt;claims&lt;/a&gt; that slow-dosing hydrogen peroxide into your aquarium can help with specific problems aquarists have. With the oxydator though, I’ve read some dubious claims about being the “freshest oxygen” 🙄 that I don’t quite udnerstand. To borrow a few marketing phrases from a few retailers’ listings:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Söchting Oxydators breakdown hydrogen peroxide into pure oxygen and water (2 H2O2—-&amp;gt;2 H20 + O2) using a special catalyst. (&lt;a href=&quot;https://www.theshrimptank.com/hardware/sochting-oxydator/&quot;&gt;The Shrimp Tank&lt;/a&gt;)&lt;/li&gt;
  &lt;li&gt;The Söchting Oxydator adds oxygen continuously and slowly to your aquarium. (TST)&lt;/li&gt;
  &lt;li&gt;The additional oxygen helps provide oxygen for aquatic critters and the beneficial bacteria in your tank. The oxygen produced is almost immediately dissolved into the water so there are little to no bubbles produced. (TST)&lt;/li&gt;
  &lt;li&gt;Due to the Oxydator’s oxidizing capacity, the water quality will be improved and harmful contaminants in the water will be neutralised and In the summer, it is effective at fighting algae. (&lt;a href=&quot;https://www.amazon.com/Sochting-Mini-Oxydator-Increase-Aquarium/dp/B0991HN6L4&quot;&gt;Amazon&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It does make tiny bubbles, which folks on YouTube have documented in their videos, and it’s cute how the shrimp like to hang around it. (&lt;a href=&quot;https://youtu.be/t3hhAbTkBAw?t=16&quot;&gt;Happy Little Whatever on YT&lt;/a&gt;)&lt;/p&gt;

&lt;h4 id=&quot;could-it-work&quot;&gt;Could it work?&lt;/h4&gt;

&lt;p&gt;It would be great to cut through all the marketing claims and get an objective breakdown of how/when it’s useful! Primarily it seems like a good way to slow-dose H₂O₂ into the tank to keep it healthy.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Again, I’m not a chemist, but by my quick calculations, maybe it &lt;em&gt;does&lt;/em&gt; work to keep oxygen levels up?&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;The &lt;a href=&quot;https://www.theshrimptank.com/hardware/sochting-oxydator/&quot;&gt;Oxydator A&lt;/a&gt; (for up to 400L aquariums) has a 150mL capacity&lt;/li&gt;
  &lt;li&gt;Which is 9mL of hydrogen peroxide (at 6% concentration)&lt;/li&gt;
  &lt;li&gt;That’s 13g of H₂O₂.&lt;/li&gt;
  &lt;li&gt;0.4 mol, since peroxide weighs 32g per mol&lt;/li&gt;
  &lt;li&gt;\(2H_2O \longrightarrow 2H_2O + O_2\), assuming all of the peroxide breaks down, instead of reacting with organic compounds in the water.&lt;/li&gt;
  &lt;li&gt;That’s 0.4 mol water, and 0.2 mol oxygen&lt;/li&gt;
  &lt;li&gt;It will turn into ~7g of water (H₂O = 18g/mol), and 6g of oxygen (O₂ = 32g/mol).&lt;/li&gt;
  &lt;li&gt;Fish like 5-8ppm of dissolved O₂. &lt;a href=&quot;https://atlas-scientific.com/blog/dissolved-oxygen-in-water-ppm-for-fish/&quot;&gt;Atlas Scientific&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;In a 50 gallon tank (10.5mol H₂O), we’d want to have 0.05mol to 0.08mol of dissolved oxygen.&lt;/li&gt;
  &lt;li&gt;Which is 1.6g to 2.5g of O₂ continuously available in the water.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;That sounds reasonable for the Sochting Oxydator to supply for at least a few weeks!&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Depending on how quickly fish consume it, and the amount of oxygen absorbed through the water’s surface, the oxydator is providing around 4x the oxygen concentration that we would want to sustain in the tank for our fish friends.&lt;/p&gt;

&lt;p&gt;If my math/chemistry calculations hold out, I could see how it’s great for adding O₂ without losing all your dissolved CO₂ to surface agitation.&lt;/p&gt;

&lt;h4 id=&quot;next-step-try-it-and-see&quot;&gt;Next step: try it and see&lt;/h4&gt;

&lt;p&gt;I want to get one of these for myself. If I do, I’ll run some tests, as methodically as I can, and share my findings here on how well the Sochting Oxydator does (or doesn’t!) work. I wouldn’t mind removing the air pump from my indoor pond, if I can get away with it.&lt;/p&gt;

&lt;p&gt;— Varun&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Who’s Varun? Currently Product at &lt;a href=&quot;https://clearme.com&quot;&gt;CLEAR&lt;/a&gt;. I’ve been in Product at &lt;a href=&quot;https://noom.com&quot;&gt;Noom&lt;/a&gt;, Hotel Tech with ALICE, now &lt;a href=&quot;https://actabl.com/&quot;&gt;Actabl&lt;/a&gt;, and I was previously founder of an HR tech startup &lt;a href=&quot;http://disqovery.com&quot;&gt;Disqovery&lt;/a&gt;. I have worn many hats, and I like making things. I also like talking business. You can reach me at &lt;a href=&quot;mailto:me@varunmehta.com&quot;&gt;me@varunmehta.com&lt;/a&gt;, &lt;a href=&quot;https://fosstodon.org/@smartperson&quot;&gt;Mastodon&lt;/a&gt;, &lt;a href=&quot;https://github.com/smartperson&quot;&gt;Github&lt;/a&gt;, and &lt;a href=&quot;https://linkedin.com/in/varunkmehta&quot;&gt;LinkedIn&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
</description>
        <pubDate>Sat, 14 Jun 2025 00:00:00 -0400</pubDate>
        <link>https://varunmehta.com/fish/aquarium/science/2025/06/14/could-the-sochting-work-i-think-so.html</link>
        <guid isPermaLink="true">https://varunmehta.com/fish/aquarium/science/2025/06/14/could-the-sochting-work-i-think-so.html</guid>
        
        
        <category>fish</category>
        
        <category>aquarium</category>
        
        <category>science</category>
        
      </item>
    
      <item>
        <title>IPv6 Learning Followups: Google and Ubiquiti (Unifi UXG) Gateways</title>
        <description>&lt;p&gt;&lt;strong&gt;The advice in this post is for more advanced computer/network users.&lt;/strong&gt; One month later, I’ve had to adjust a few things for our IPv6 setup. And rather than posting it exclusively on corporate social media, I’m writing it up here and sharing on the fediverse (Mastodon).&lt;/p&gt;

&lt;h4 id=&quot;recap&quot;&gt;Recap&lt;/h4&gt;

&lt;p&gt;&lt;a href=&quot;/technology/2024/08/27/ipv6-learning-and-adoption.html&quot;&gt;Previously&lt;/a&gt;, I went through the certifications to get /64 and /48 reserved IPv6 spaces from Hurricane Electric, and set up a dual stack configuration for our whole home using HE’s free &lt;a href=&quot;https://tunnelbroker.net&quot;&gt;tunnelbroker.net&lt;/a&gt; service.&lt;/p&gt;

&lt;p&gt;Overall, things have been working well. I was skeptical of what speeds to expect, but given that HE runs a huge part of the internet backbone, and the tunnel entry point is local to us in New York, it’s no surprise that latency and throughput are just as good as our ISP’s direct IPv4 service.&lt;/p&gt;

&lt;h4 id=&quot;solving-the-google-ipv6-tunnel-problem-with-my-pi-hole&quot;&gt;Solving the Google IPv6 Tunnel Problem with my Pi Hole&lt;/h4&gt;

&lt;p&gt;As mentioned in the last post, Google seems to treat any IPv6 tunnel user as a malicious actor/VPN/shady person, and the whole family has been struggling with increasingly impossible CAPTCHAs if they want to run a Google web search. YouTube, Gmail, and other Google services work without issue.&lt;/p&gt;

&lt;p&gt;They’ve mostly migrated over the DuckDuckGo as their default search engines, but I still wanted to provide some relief for when Google search was necessary. Thankfully, since I run a &lt;a href=&quot;https://pi-hole.net/&quot;&gt;Pi Hole&lt;/a&gt; locally, I can control how any domain name resolves on our home LAN.&lt;/p&gt;

&lt;p&gt;Set up a Pi Hole rule to specifically block IPv6 (AAAA record) DNS resolution for all *.google.com domains.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Domain/RegEx: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(\.|^)google\.com;querytype=AAAA&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;Type: Regex blacklist&lt;/li&gt;
  &lt;li&gt;Status: Enabled&lt;/li&gt;
  &lt;li&gt;Comment: “No Google IPv6” or something else that will help you remember&lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&quot;ubiquiti-edgerouter-x-ipv6-firewall-rules-are-awkward&quot;&gt;Ubiquiti EdgeRouter X IPv6 Firewall Rules are Awkward&lt;/h4&gt;
&lt;p&gt;Since every IPv6 address is publicly routable, it is &lt;strong&gt;vital&lt;/strong&gt; that you keep good firewall rules in place. We cannot rely on NAT+Port Forwarding for protection the way we do for IPv4 LANs.&lt;/p&gt;

&lt;p&gt;I guess since the EdgeRouter X/Max series is older, it only has a GUI for creating IPv4 firewall rules. If you want IPv6 firewall rules, then you need to add them using the console or the config tree. This is okay, but I found it awkward to set up and validate that the rules were working as expected. Coupled with the fact that the aging EdgeRouter X was under high CPU load with all of our LAN traffic, and I used this as an excuse to upgrade to Ubiquiti’s newest line of Unifi-compatible gateways. Specificaly the &lt;a href=&quot;https://store.ui.com/us/en/products/uxg-max&quot;&gt;5-port UXG Max&lt;/a&gt;. It would be easier to manage it together with our Unifi wireless access point, ande it has a nice-looking and functional GUI for setting up IPv4 and IPv6 firewall rules. &lt;strong&gt;However…&lt;/strong&gt;&lt;/p&gt;

&lt;h4 id=&quot;uniquiti-unifi-uxg-gateways-dont-natively-support-ipv6-tunnels&quot;&gt;Uniquiti Unifi UXG Gateways don’t natively support IPv6 Tunnels!&lt;/h4&gt;

&lt;p&gt;My bad for assuming that a newer product would &lt;em&gt;at least&lt;/em&gt; be as capable as the older (cheaper) product, I guess. There are no GUI tools for creating a tunnel, and there is no config tree create whatever you need, either. Ubiquiti Support confirmed to me that IPv6 tunnels are not supported on the UXG series :-(&lt;/p&gt;

&lt;h5 id=&quot;add-an-ipv6-tunnel-to-the-uxg-max-anyway&quot;&gt;Add an IPv6 Tunnel to the UXG Max &lt;em&gt;anyway&lt;/em&gt;&lt;/h5&gt;

&lt;p&gt;We can still SSH into the gateway, so there has to be a way. Turns out there is, and it works better than I expected, once you jump through some setup hurdles. Basically:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Modify and set up the UDM &lt;a href=&quot;https://github.com/unifi-utilities/unifios-utilities/tree/main/on-boot-script-2.x&quot;&gt;on_boot.d&lt;/a&gt; daemon, which will run whatever scripts you want on boot.&lt;/li&gt;
  &lt;li&gt;Add the &lt;a href=&quot;https://github.com/telnetdoogie/UDMP-ipv6&quot;&gt;UDMP-ipv6 startup scripts&lt;/a&gt; to on_boot.d, to set up the IPv6 tunnel on every boot.&lt;/li&gt;
  &lt;li&gt;Set up your IPv6 firewall rules on the Unifi GUI as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Internet v6 In&lt;/code&gt; like a normal person would.&lt;/li&gt;
  &lt;li&gt;Add the UDMP-ipv6 cron jobs. That will keep copying Internet v6 firewall rules over to the correct interface.&lt;/li&gt;
&lt;/ol&gt;

&lt;h6 id=&quot;1-modify-and-install-on_bootd-on-the-uxg&quot;&gt;1. Modify and install on_boot.d on the UXG&lt;/h6&gt;
&lt;p&gt;This is the most technically complex part. Do this right and the rest is smooth sailing.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://help.ui.com/hc/en-us/articles/204909374-UniFi-Connect-with-Debug-Tools-SSH&quot;&gt;Set up SSH console access to your UXG device&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Follow the instructions in the &lt;a href=&quot;https://github.com/unifi-utilities/unifios-utilities/tree/main/on-boot-script-2.x&quot;&gt;UDM Boot Script Readme&lt;/a&gt; for a remote installation.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;strong&gt;NOTE:&lt;/strong&gt; If you are using a UXG device, as of this date, the maintainer has not added support for it in the installation script. You can check out my open &lt;a href=&quot;https://github.com/unifi-utilities/unifios-utilities/pull/631/files&quot;&gt;pull request&lt;/a&gt; to see how to edit the script for your own device. Run &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ubnt-device-info model&lt;/code&gt; on your device to get the correct model string to use on &lt;a href=&quot;https://github.com/unifi-utilities/unifios-utilities/pull/631/files#diff-7d14b9e47ed349ffcb22db70dc9b5c704a1859ff39566bd43fa7c5366c3ad893R78&quot;&gt;line 78&lt;/a&gt;. You will have to copy a modified version of the remote_install.sh script over to your UXG and run it from there.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h5 id=&quot;2-add-ipv6-tunnel-startup-scripts&quot;&gt;2. Add IPv6 tunnel startup scripts&lt;/h5&gt;
&lt;p&gt;Add the &lt;a href=&quot;https://github.com/telnetdoogie/UDMP-ipv6&quot;&gt;UDMP-ipv6 startup scripts&lt;/a&gt; to your boot scripts. Follow the instructions in that README. No modifications are necessary for UXG.&lt;/p&gt;

&lt;h5 id=&quot;3-set-up-ipv6-firewall-rules&quot;&gt;3. Set up IPv6 firewall rules&lt;/h5&gt;
&lt;p&gt;Do this as normal using the Unifi GUI to set them up as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Internet v6 In&lt;/code&gt; advanced rules or else simple rules. Both will work.&lt;/p&gt;

&lt;h5 id=&quot;4-add-the-udmp-ipv6-cronjobs&quot;&gt;4. Add the UDMP-ipv6 cronjobs&lt;/h5&gt;
&lt;p&gt;This is also documented in the README inside &lt;a href=&quot;https://github.com/telnetdoogie/UDMP-ipv6&quot;&gt;UDMP-ipv6 startup scripts&lt;/a&gt;. This wil make sure that your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Internet v6 In&lt;/code&gt; rules will apply to the IPv6 traffic that’s coming into your tunnel.&lt;/p&gt;

&lt;h5 id=&quot;whats-next&quot;&gt;What’s Next&lt;/h5&gt;
&lt;p&gt;Nothing is left here! I might contribute to the community-developed &lt;a href=&quot;https://github.com/unifi-utilities/unifios-utilities/&quot;&gt;Unifi OS Utilities&lt;/a&gt; project to help get it working for UXG devices, too.&lt;/p&gt;

&lt;p&gt;— Varun&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Who’s Varun? I’ve been in Product at &lt;a href=&quot;https://noom.com&quot;&gt;Noom&lt;/a&gt;, and I was previously founder of an HR tech startup &lt;a href=&quot;http://disqovery.com&quot;&gt;Disqovery&lt;/a&gt;. I have worn many hats, and I like making things. I also like talking business. You can reach me at &lt;a href=&quot;mailto:me@varunmehta.com&quot;&gt;me@varunmehta.com&lt;/a&gt;, &lt;a href=&quot;https://fosstodon.org/@smartperson&quot;&gt;Mastodon&lt;/a&gt;, &lt;a href=&quot;https://github.com/smartperson&quot;&gt;Github&lt;/a&gt;, and &lt;a href=&quot;https://linkedin.com/in/varunkmehta&quot;&gt;LinkedIn&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
</description>
        <pubDate>Mon, 07 Oct 2024 00:00:00 -0400</pubDate>
        <link>https://varunmehta.com/technology/2024/10/07/ipv6-learning-followups.html</link>
        <guid isPermaLink="true">https://varunmehta.com/technology/2024/10/07/ipv6-learning-followups.html</guid>
        
        
        <category>Technology</category>
        
      </item>
    
      <item>
        <title>Getting Caught Up On IPv6</title>
        <description>&lt;p&gt;A couple of things came up recently that got me thinking about IPv6 and how little I understood beyond the absolute basics. I figured that now is as good a time as any to learn and see what I can do to get involved. Here’s what I accomplished and what I still have to do.&lt;/p&gt;

&lt;h4 id=&quot;why-now&quot;&gt;Why now?&lt;/h4&gt;

&lt;p&gt;&lt;em&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/IPv6&quot;&gt;IPv6 was drafted 1998, and ratified by the IETF in 2017.&lt;/a&gt; Why am I looking at this now?&lt;/em&gt; A few things came up around the same time that brought IPv6 back to the forefront for me:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Home automation.&lt;/strong&gt; I got a new &lt;a href=&quot;https://us.govee.com/products/goveelife-42-smart-tower-fan-2-max&quot;&gt;smart home fan&lt;/a&gt; which supports &lt;a href=&quot;https://en.wikipedia.org/wiki/Matter_(standard)&quot;&gt;Matter&lt;/a&gt;, because of course I’m a nerd. The fan comes with a note that in order to control it using a Matter platform (like Apple Home), it &lt;strong&gt;the fan must be connected to the network over IPv6&lt;/strong&gt;. I’ve never encountered this requirement for any device before.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;My email server supports IPv6.&lt;/strong&gt; As I wrote recently, I’ve set up my own &lt;a href=&quot;2024-08-12-serving-my-own-email-and-more.html&quot;&gt;personal email server&lt;/a&gt; using &lt;a href=&quot;https://mailinabox.email&quot;&gt;Mail-in-a-Box&lt;/a&gt;. During the setup process I noticed that Contabo (the VPS host) and the full Mail-in-a-Box stack support and provide IPv6 connectivity. When resolving the server’s issues it got me to set up all of the DNS records, reverse DNS records, etc. for both the IPv4 address &lt;em&gt;and&lt;/em&gt; the IPv6 address. That’s pretty cool.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&quot;an-opportunity-a-roadblock-and-an-opportunity&quot;&gt;An Opportunity, a Roadblock, and an Opportunity&lt;/h4&gt;

&lt;p&gt;To learn more about IPv6, the legendary Hurricane Electric runs a fun and free &lt;a href=&quot;https://ipv6.he.net/&quot;&gt;IPv6 certificate program&lt;/a&gt;. It’s a mixture of practical tests and quizzes that get progressively more difficult for each of the 7 levels, starting at being an IPv6 Newbie all the way up to being an IPv6 Sage. There are video tutorials and online resources to help you learn what you need to know to pass each level.&lt;/p&gt;

&lt;p&gt;Unfortunately, I hit an obstacle while getting up and running with IPv6. Early in the certificate program you need to have both a server that runs on IPv6 and &lt;em&gt;also&lt;/em&gt; a desktop that can reach IPv6 locations. I had the server covered thanks to my Contabo VPS. However, the desktop was a challenge. You see, Spectrum, my ISP for my NYC apartment, supports IPv6, but our ISP for our Long Island home, Altice/Optimum, &lt;em&gt;does not&lt;/em&gt;, and has no definite plans to support IPv6 any time soon.&lt;/p&gt;

&lt;p&gt;Thankfully, Hurricane Electric also offers a &lt;a href=&quot;https://tunnelbroker.net/&quot;&gt;free IPv6 tunnel service&lt;/a&gt; where you can get your own /64 block of routable IPv6 addresses for free. That’s &lt;strong&gt;18 pentillion&lt;/strong&gt; usable, public IPv6 addresses, just for me :-)&lt;/p&gt;

&lt;h4 id=&quot;tunnel-notes&quot;&gt;Tunnel Notes&lt;/h4&gt;

&lt;p&gt;Getting the tunnel set up on my Ubiquiti EdgeMAX was easy enough once I noticed they had specific instructions to follow to get it operational. They have example configurations for many common routers and operating systems. And a little bit of poking around on the internet and you’re bound to find someone who has your particular configuration. After all, they now provide &lt;a href=&quot;https://tunnelbroker.net/usage/tunnels_by_country.php&quot;&gt;56,000 IPv4 to 6 tunnels&lt;/a&gt;!&lt;/p&gt;

&lt;h5 id=&quot;certification-success&quot;&gt;Certification Success!&lt;/h5&gt;

&lt;p&gt;&lt;img src=&quot;//ipv6.he.net/certification/create_badge.php?pass_name=varunmehta&amp;amp;badge=2&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Once the tunnel was set up, most of the certificate course was easy enough to progress through. There were a few tricky questions I had to look up, and a few specific steps to take to get my IPv6 desktop and server environments set up and totally ship-shape. The learning was fun, and some of the questions/snark in the certificate course was fun, too.&lt;/p&gt;

&lt;h5 id=&quot;whats-next&quot;&gt;What’s Next&lt;/h5&gt;

&lt;p&gt;&lt;strong&gt;Free t-shirt!&lt;/strong&gt; Apparently Hurricane Electric sends a nerdy-looking t-shirt to folks who finish their course to the Sage level. I didn’t know this until I finished the certificate program. I look forward to receiving it and showing it off.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Unexpected IP issues with major servers, to be resolved.&lt;/strong&gt; Unfortunately, now that our family home is on IPv6, it’s triggering some security protections on websites like Google. While I don’t mind much, as I’ve been minimizing how much I use Google (including Gmail), the rest of the family has noticed and is being irked by the fact they need to solve a Google CAPTCHA a couple of times a day for each device they use. Some HE tunnel users &lt;a href=&quot;https://forums.he.net/index.php?topic=4253.0&quot;&gt;also noticed this issue&lt;/a&gt;, and I have to get caught up on the discussion and figure out if any of these will help us. It might be as simple as just waiting for Google to realize that we’re legit, I don’t know.&lt;/p&gt;

&lt;p&gt;— Varun&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Who’s Varun? I’ve been in Product at &lt;a href=&quot;https://noom.com&quot;&gt;Noom&lt;/a&gt;, and I was previously founder of an HR tech startup &lt;a href=&quot;http://disqovery.com&quot;&gt;Disqovery&lt;/a&gt;. I have worn many hats, and I like making things. I also like talking business. You can reach me at &lt;a href=&quot;mailto:me@varunmehta.com&quot;&gt;me@varunmehta.com&lt;/a&gt;, &lt;a href=&quot;https://fosstodon.org/@smartperson&quot;&gt;Mastodon&lt;/a&gt;, &lt;a href=&quot;https://github.com/smartperson&quot;&gt;Github&lt;/a&gt;, and &lt;a href=&quot;https://linkedin.com/in/varunkmehta&quot;&gt;LinkedIn&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
</description>
        <pubDate>Tue, 27 Aug 2024 00:00:00 -0400</pubDate>
        <link>https://varunmehta.com/technology/2024/08/27/ipv6-learning-and-adoption.html</link>
        <guid isPermaLink="true">https://varunmehta.com/technology/2024/08/27/ipv6-learning-and-adoption.html</guid>
        
        
        <category>Technology</category>
        
      </item>
    
      <item>
        <title>Serving My Own Email and More</title>
        <description>&lt;p&gt;It feels like in the middle of the corporate-influenced internet’s heydey, we’re seeing a re-decentralized internet’s nascent emergence. And for me, I think it’s time to figure out and free one of the oldest parts of the internet I still use regularly: e-mail. Thankfully, it’s easier now than it’s ever been.&lt;/p&gt;

&lt;ul id=&quot;markdown-toc&quot;&gt;
  &lt;li&gt;&lt;a href=&quot;#why&quot; id=&quot;markdown-toc-why&quot;&gt;Why?&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#requirements&quot; id=&quot;markdown-toc-requirements&quot;&gt;Requirements&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#the-approach&quot; id=&quot;markdown-toc-the-approach&quot;&gt;The Approach&lt;/a&gt;    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#services&quot; id=&quot;markdown-toc-services&quot;&gt;Services&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#software&quot; id=&quot;markdown-toc-software&quot;&gt;Software&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#guides&quot; id=&quot;markdown-toc-guides&quot;&gt;Guides&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#tips--tricks&quot; id=&quot;markdown-toc-tips--tricks&quot;&gt;Tips &amp;amp; Tricks&lt;/a&gt;    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#name-server-daemon-nsd-v4-wont-start&quot; id=&quot;markdown-toc-name-server-daemon-nsd-v4-wont-start&quot;&gt;Name Server Daemon (NSD) v4 won’t start&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#ui-and-validation-errors-for-clouds3-email-backup&quot; id=&quot;markdown-toc-ui-and-validation-errors-for-clouds3-email-backup&quot;&gt;UI and validation errors for cloud/S3 email backup&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#follow-the-built-in-advice&quot; id=&quot;markdown-toc-follow-the-built-in-advice&quot;&gt;Follow the built-in advice&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#hows-it-going&quot; id=&quot;markdown-toc-hows-it-going&quot;&gt;How’s it going?&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#now-what&quot; id=&quot;markdown-toc-now-what&quot;&gt;Now What?&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&quot;why&quot;&gt;Why?&lt;/h4&gt;

&lt;p&gt;&lt;em&gt;Why not?&lt;/em&gt; Seriously though, my reasons are manyfold:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;No limits.&lt;/strong&gt; I’m tired of the regular reminders from Google to upgrade my storage, while getting casual warnings that I might stop receiving email unless I clear up my storage or start paying them for more room in my inbox. If I run my own email, I can provide my own storage, and upgrade it however/whenever I see fit.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;No ads.&lt;/strong&gt; I don’t like seeing Google-inserted ads in my inbox.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Privacy, please.&lt;/strong&gt; Google/Alphabet/Microsoft/Apple already know enough about me without also having access to all of my email. The less data floating out there on the corporate-controlled internet, the happier I’ll be.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Ownership feels good.&lt;/strong&gt; More philosophically, I own the domains. If I own the domain, the email should be mine, too.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Bonuses and network effects, abound.&lt;/strong&gt; Depending on the exact approach, there could be more benefits to running my own email, that would apply to other aspects of my online/tech footprint.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&quot;requirements&quot;&gt;Requirements&lt;/h4&gt;

&lt;p&gt;A good approach has to satisfy a few key requirements for me:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Keep costs “reasonable”.&lt;/strong&gt; For now I’m looking to pay around the same price as a storage upgrade from any of the major consumer cloud providers (Apple/Google/Microsoft). I think $5-10 a month sounds about right.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Occasional maintenance at most.&lt;/strong&gt; I want an email system that is mostly “set and forget” for its core features. I’m okay with occasionally needing to log in to change settings, upgrade, or perform other maintenance tasks – let’s say maybe every two weeks or so. A good system will notify me when maintenance is required instead of making me poke aorund and check for myself.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Won’t get marked as spam.&lt;/strong&gt; I need to be able to set up all of the proofs, validations, and security so that a receiving email server (especially gmail) won’t mark my email or domain as spam.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Reliable enough that I can use it for important email.&lt;/strong&gt; Servers go down and I want to be prepared for that. In particular, I want to make sure incoming emails don’t get lost/bounce if my server goes offline for a little bit due to maintenance or an outage.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Data integrity so I don’t lose what I already have.&lt;/strong&gt; In particular, I need some kind of automated backup system in place so I don’t lose my whole inbox because of a storage failure.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Tinkerability, please.&lt;/strong&gt; Good off-the-shelf solutions are cool. I still want something with some bells and whistles and knobs to play with to keep things interesting.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&quot;the-approach&quot;&gt;The Approach&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;tl;dr:&lt;/strong&gt; The core of my approach will be using &lt;a href=&quot;https://mailinabox.email/&quot;&gt;Mail in a Box&lt;/a&gt; on a Virtual Private Server. Set up backup storage and backup DNS, too.
&lt;a href=&quot;https://mailinabox.email/&quot;&gt;
  &lt;img src=&quot;https://res.cloudinary.com/varun-blog/image/fetch/c_limit,f_auto,q_auto,w_800/https://varunmehta.com/img/2024/08/mail-in-a-box.png&quot; srcset=&quot;https://res.cloudinary.com/varun-blog/image/fetch/c_limit,f_auto,q_auto,w_320/https://varunmehta.com/img/2024/08/mail-in-a-box.png 320w, https://res.cloudinary.com/varun-blog/image/fetch/c_limit,f_auto,q_auto,w_640/https://varunmehta.com/img/2024/08/mail-in-a-box.png 640w, https://res.cloudinary.com/varun-blog/image/fetch/c_limit,f_auto,q_auto,w_960/https://varunmehta.com/img/2024/08/mail-in-a-box.png 960w, https://res.cloudinary.com/varun-blog/image/fetch/c_limit,f_auto,q_auto,w_1280/https://varunmehta.com/img/2024/08/mail-in-a-box.png 1280w, https://res.cloudinary.com/varun-blog/image/fetch/c_limit,f_auto,q_auto,w_1600/https://varunmehta.com/img/2024/08/mail-in-a-box.png 1600w&quot; sizes=&quot;(min-width: 50rem) 50rem, 90&quot; width=&quot;2272&quot; height=&quot;414&quot; crossorigin=&quot;anonymous&quot; /&gt;
&lt;/a&gt;&lt;/p&gt;

&lt;h5 id=&quot;services&quot;&gt;Services&lt;/h5&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://contabo.com/en-us/vps/&quot;&gt;Contabo Virtual Private Server (VPS)&lt;/a&gt; to run the server
    &lt;ul&gt;
      &lt;li&gt;$6.85/mo.&lt;/li&gt;
      &lt;li&gt;US East Location&lt;/li&gt;
      &lt;li&gt;4 vCPUs, 6GB RAM, 130GB NVME Storage&lt;/li&gt;
      &lt;li&gt;32TB monthly outbound traffic, unlimited inbound traffic&lt;/li&gt;
      &lt;li&gt;I thought about running the server on one of my own computers at home that’s connected 24/7. However I decided against it&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://contabo.com/en-us/object-storage/&quot;&gt;Contabo Object Storage&lt;/a&gt; for offsite backup
    &lt;ul&gt;
      &lt;li&gt;$2.99/mo.&lt;/li&gt;
      &lt;li&gt;US Central Location&lt;/li&gt;
      &lt;li&gt;250GB, S3-compatible API&lt;/li&gt;
      &lt;li&gt;This is enough storage to last a long time for backups, and maybe also to store other stuff if/when I need to.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.gandi.net/en-US&quot;&gt;Gandi DNS&lt;/a&gt; for Domain Name registrar and hosting
    &lt;ul&gt;
      &lt;li&gt;$24/yr.&lt;/li&gt;
      &lt;li&gt;Supports Glue Nameservers&lt;/li&gt;
      &lt;li&gt;Supports DNSSEC (for extra email deliverability)&lt;/li&gt;
      &lt;li&gt;Supports Advanced DNS configuration&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;Backups DNSes in case the main server goes down
    &lt;ul&gt;
      &lt;li&gt;Free&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;https://freedns.afraid.org/secondary/instructions.php&quot;&gt;afraid.org&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;https://puck.nether.net/dns/login&quot;&gt;nether.net&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;As long as the backup DNS provides the MX records, any incoming email will be held by the sending server and retried until my email server comes back online.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h5 id=&quot;software&quot;&gt;Software&lt;/h5&gt;

&lt;p&gt;&lt;a href=&quot;https://mailinabox.email/&quot;&gt;Mail in a Box&lt;/a&gt; v69b is giving us the bulk of what we need. It comes with a bunch of components already set up. Just take a look at the &lt;a href=&quot;https://mailinabox.email/static/architecture.svg&quot;&gt;architecture diagram&lt;/a&gt;! &lt;img src=&quot;https://mailinabox.email/static/architecture.svg&quot; alt=&quot;architecture diagram&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Requires minimal configuration out of the box&lt;/li&gt;
  &lt;li&gt;Runs on top of a fresh Ubuntu install&lt;/li&gt;
  &lt;li&gt;Sends and receives email&lt;/li&gt;
  &lt;li&gt;Provides good webmail (&lt;a href=&quot;https://roundcube.net/&quot;&gt;Roundcube&lt;/a&gt;)&lt;/li&gt;
  &lt;li&gt;Serves its own DNS for all of the domains you want to serve&lt;/li&gt;
  &lt;li&gt;Hosts static web pages on any domains&lt;/li&gt;
  &lt;li&gt;Preconfigured &lt;a href=&quot;https://nextcloud.com/&quot;&gt;NextCloud&lt;/a&gt; for contacts, andcalendar&lt;/li&gt;
  &lt;li&gt;Automatically manages backups locally or in an S3 bucket&lt;/li&gt;
  &lt;li&gt;Uses Z-Push for push email/calendar support on iOS&lt;/li&gt;
  &lt;li&gt;Generates activity and status emails so I know about any errors or if maintenance is required&lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&quot;guides&quot;&gt;Guides&lt;/h4&gt;

&lt;p&gt;I’m not going to write up a full guide on MiaB, both because a good one exists, and because as soon as I write it, it will be out of date. They have a &lt;a href=&quot;https://mailinabox.email/guide.html&quot;&gt;setup guide&lt;/a&gt; that you should follow. Without the issues I mentioned in “Tips &amp;amp; Tricks” it would have taken me only a couple of hours to finish getting my email server totally up and running.&lt;/p&gt;

&lt;h4 id=&quot;tips--tricks&quot;&gt;Tips &amp;amp; Tricks&lt;/h4&gt;

&lt;p&gt;I ran into a few snags along the way. I document them here in case you’re looking for these terms on your web searches.&lt;/p&gt;

&lt;h5 id=&quot;name-server-daemon-nsd-v4-wont-start&quot;&gt;Name Server Daemon (NSD) v4 won’t start&lt;/h5&gt;

&lt;p&gt;If you get this error, and NSD won’t start, be on the alert! Your DNS won’t be served because the daemon isn’t running! You’ll see errors on the console or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;journalctl&lt;/code&gt; like:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;nsd can&apos;t bind tcp socket: Cannot assign requested address
nsd.service: Start request repeated too quickly.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href=&quot;https://discourse.mailinabox.email/t/nsd-issue-when-ipv6-is-disabled/10873&quot;&gt;This happened to me because IPv6 is off&lt;/a&gt;. The Name Server Daemon (NSD) that comes with Mail in a Box requires IPv6 is working on your server, and it will entirely &lt;em&gt;fail&lt;/em&gt; to launch if it can’t. My VPS build came with IPv6 off. Rather than disabling IPv6 for NSD, I figured it’s smarter to just enable IPv6. It’s the future, after all.&lt;/p&gt;

&lt;p&gt;Turning on IPv6 was simple. I just edited &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/etc/sysctl.conf&lt;/code&gt; to include the line below and restarted:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;net.ipv6.conf.all.disable_ipv6 = 0
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h5 id=&quot;ui-and-validation-errors-for-clouds3-email-backup&quot;&gt;UI and validation errors for cloud/S3 email backup&lt;/h5&gt;

&lt;p&gt;Cloud backups in Mail in a Box have some UI quirks when want to back up in S3. Thankfully this was well documented on the &lt;a href=&quot;https://discourse.mailinabox.email/t/unable-to-setup-s3-backups/9906/3&quot;&gt;MiaB forums&lt;/a&gt; by syslarper. Here are a few paraphrased notes from them. Refer to image below to see the fields I’m referring to.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Omit the protocol (http/https) and port numbers on the S3 Host/Endpoint.&lt;/li&gt;
  &lt;li&gt;Omit the region name.&lt;/li&gt;
  &lt;li&gt;Only port 443 is supported.&lt;/li&gt;
  &lt;li&gt;You need to have already created the S3 bucket.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After validating and saving your settings, most of the configuration fields will be empty when you open the backups management page. Just trust that the backup is going to work, and confirm it by checking the logs and peeking in your S3 bucket the next day :-)&lt;/p&gt;

&lt;div class=&quot;mdl-grid&quot;&gt;

  
  &lt;div class=&quot;mdl-cell mdl-cell--4-col&quot;&gt;
  
    
    &lt;a href=&quot;/img/2024/08/backup-ui-bugs.png&quot; data-lightbox=&quot;mail-in-a-box-setup&quot; title=&quot;I _did_ fill in all of these fields for the S3 backup, and it works as expected. However, the UI won&apos;t show most of the values, so it looks broken/incomplete. I have no idea why, other than it being a bug.&quot;&gt;
      
        &lt;img src=&quot;https://res.cloudinary.com/varun-blog/image/fetch/c_limit,f_auto,q_auto,w_800/https://varunmehta.com/img/2024/08/backup-ui-bugs.png&quot; srcset=&quot;https://res.cloudinary.com/varun-blog/image/fetch/c_limit,f_auto,q_auto,w_320/https://varunmehta.com/img/2024/08/backup-ui-bugs.png 320w,
https://res.cloudinary.com/varun-blog/image/fetch/c_limit,f_auto,q_auto,w_640/https://varunmehta.com/img/2024/08/backup-ui-bugs.png 640w,
https://res.cloudinary.com/varun-blog/image/fetch/c_limit,f_auto,q_auto,w_960/https://varunmehta.com/img/2024/08/backup-ui-bugs.png 960w,
https://res.cloudinary.com/varun-blog/image/fetch/c_limit,f_auto,q_auto,w_1280/https://varunmehta.com/img/2024/08/backup-ui-bugs.png 1280w,
https://res.cloudinary.com/varun-blog/image/fetch/c_limit,f_auto,q_auto,w_1600/https://varunmehta.com/img/2024/08/backup-ui-bugs.png 1600w&quot; sizes=&quot;(min-width: 50rem) 50rem, 90&quot; width=&quot;2338&quot; height=&quot;1350&quot; crossorigin=&quot;anonymous&quot; /&gt;
      
    &lt;/a&gt;
  &lt;/div&gt;

  
  &lt;div class=&quot;mdl-cell mdl-cell--4-col&quot;&gt;
  
    
    &lt;a href=&quot;/img/2024/08/mail-in-a-box-checks.png&quot; data-lightbox=&quot;mail-in-a-box-setup&quot; title=&quot;The status checks screen will help you take care of everything you need to until you finally get to All OK. Trust.&quot;&gt;
      
        &lt;img src=&quot;https://res.cloudinary.com/varun-blog/image/fetch/c_limit,f_auto,q_auto,w_800/https://varunmehta.com/img/2024/08/mail-in-a-box-checks.png&quot; srcset=&quot;https://res.cloudinary.com/varun-blog/image/fetch/c_limit,f_auto,q_auto,w_320/https://varunmehta.com/img/2024/08/mail-in-a-box-checks.png 320w,
https://res.cloudinary.com/varun-blog/image/fetch/c_limit,f_auto,q_auto,w_640/https://varunmehta.com/img/2024/08/mail-in-a-box-checks.png 640w,
https://res.cloudinary.com/varun-blog/image/fetch/c_limit,f_auto,q_auto,w_960/https://varunmehta.com/img/2024/08/mail-in-a-box-checks.png 960w,
https://res.cloudinary.com/varun-blog/image/fetch/c_limit,f_auto,q_auto,w_1280/https://varunmehta.com/img/2024/08/mail-in-a-box-checks.png 1280w,
https://res.cloudinary.com/varun-blog/image/fetch/c_limit,f_auto,q_auto,w_1600/https://varunmehta.com/img/2024/08/mail-in-a-box-checks.png 1600w&quot; sizes=&quot;(min-width: 50rem) 50rem, 90&quot; width=&quot;1928&quot; height=&quot;428&quot; crossorigin=&quot;anonymous&quot; /&gt;
      
    &lt;/a&gt;
  &lt;/div&gt;

&lt;/div&gt;

&lt;h5 id=&quot;follow-the-built-in-advice&quot;&gt;Follow the built-in advice&lt;/h5&gt;

&lt;p&gt;Use the status checks in Mail in a Box to see what else you need to do to get everything working properly. &lt;strong&gt;Also&lt;/strong&gt; use &lt;a href=&quot;https://www.mail-tester.com/&quot;&gt;Mail-Tester&lt;/a&gt; to validate that your emails are getting sent with all the right security bits set to improve deliverability!&lt;/p&gt;

&lt;h4 id=&quot;hows-it-going&quot;&gt;How’s it going?&lt;/h4&gt;

&lt;p&gt;Having been over a week, it’s so far so good! Backups are getting generated. When the server is down, incoming emails are just delayed for a few minutes while the sending server waits to retry. Everything costs me about $10/month.&lt;/p&gt;

&lt;h4 id=&quot;now-what&quot;&gt;Now What?&lt;/h4&gt;

&lt;p&gt;Now we get to have some fun! Send me an email if you want to help me test things out. Also, since MiaB is set up to serve static pages, I was able to migrate my Jekyll git repos and hooks over to the server. Which means that even this blog you’re reading (right now at varunmehta.com!) is published and hosted on the very same server. I might even cancel my account with &lt;a href=&quot;https://www.dreamhost.com&quot;&gt;Dreamhost&lt;/a&gt;, my discount shared hosting provider!&lt;/p&gt;

&lt;p&gt;— Varun&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Who’s Varun? I’ve been in Product at &lt;a href=&quot;https://noom.com&quot;&gt;Noom&lt;/a&gt;, and I was previously founder of an HR tech startup &lt;a href=&quot;http://disqovery.com&quot;&gt;Disqovery&lt;/a&gt;. I have worn many hats, and I like making things. I also like talking business. You can reach me at &lt;a href=&quot;mailto:me@varunmehta.com&quot;&gt;me@varunmehta.com&lt;/a&gt;, &lt;a href=&quot;https://fosstodon.org/@smartperson&quot;&gt;Mastodon&lt;/a&gt;, &lt;a href=&quot;https://github.com/smartperson&quot;&gt;Github&lt;/a&gt;, and &lt;a href=&quot;https://linkedin.com/in/varunkmehta&quot;&gt;LinkedIn&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
</description>
        <pubDate>Mon, 12 Aug 2024 00:00:00 -0400</pubDate>
        <link>https://varunmehta.com/technology/2024/08/12/serving-my-own-email-and-more.html</link>
        <guid isPermaLink="true">https://varunmehta.com/technology/2024/08/12/serving-my-own-email-and-more.html</guid>
        
        
        <category>Technology</category>
        
      </item>
    
      <item>
        <title>FPGA Game Graphics Part 0: Motivation</title>
        <description>&lt;p&gt;FPGA hardware has been getting a lot more affordable, and I have a few ideas for projects. The new low-cost boards that combine FPGA-style programmable logic with a fast processor are especially interesting. But before I can get to the more advanced stuff, I need to re-learn the basics. I need a plan.&lt;/p&gt;

&lt;ul id=&quot;markdown-toc&quot;&gt;
  &lt;li&gt;&lt;a href=&quot;#why&quot; id=&quot;markdown-toc-why&quot;&gt;Why?&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#goals&quot; id=&quot;markdown-toc-goals&quot;&gt;Goals&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#hardware&quot; id=&quot;markdown-toc-hardware&quot;&gt;Hardware&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#requirements&quot; id=&quot;markdown-toc-requirements&quot;&gt;Requirements&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#challenges&quot; id=&quot;markdown-toc-challenges&quot;&gt;Challenges&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#follow-along&quot; id=&quot;markdown-toc-follow-along&quot;&gt;Follow along!&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&quot;why&quot;&gt;Why?&lt;/h4&gt;

&lt;p&gt;&lt;em&gt;Why not?&lt;/em&gt; My day jobs keeps me far away from low-level technology, and that’s fine with me. However, as a hobby, it’s an awful lot of fun. I find I learn best by picking a goal and learning as I go along to make it happen.&lt;/p&gt;

&lt;h4 id=&quot;goals&quot;&gt;Goals&lt;/h4&gt;

&lt;p&gt;I actually just want to make a clock. The thing is, I like making strange clocks. Some examples:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;An Arduino PWM-based clock that uses analog VU-style meters to show hours/minutes/seconds.&lt;/li&gt;
  &lt;li&gt;A Raspberry Pi running QEMU, emulating a PPC running Mac OS 9. The Mac OS 9 desktop is displayed on an e-ink display. The Mac OS desktop naturally displays the time in the menu bar. However, within the virtual machine, Internet Explorer 5 auto-refreshes a webpage hosted by the Raspberry Pi – a simple webpage that displays current and upcoming weather conditions. &lt;em&gt;This might be worth a post of its own one day.&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;My strange clock game has been ramping up. People are building some &lt;a href=&quot;https://hackaday.com/2019/06/13/a-tetris-clock/&quot;&gt;tetris-style clocks&lt;/a&gt; which are pretty cool, and that got me thinking about making my own video game-styled clock. But I’m a visual nerd, and smooth graphics are important to me. There are plenty of interesting systems out there like the Odroid Go that even include a display, buttons, and a battery, but pretty much all of these systems have limitations that I don’t like. A big one is the quality of the visuals: these devices include Serial/SPI based displays, and SoCs like the ESP32 don’t include the kind of graphics hardware necessary to provide smooth (60 frames per second) motion at resolutions higher than 240x160.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://res.cloudinary.com/varun-blog/image/fetch/c_limit,f_auto,q_auto,w_800/https://varunmehta.com/img/2020/05/odroid-go.gif&quot; srcset=&quot;https://res.cloudinary.com/varun-blog/image/fetch/c_limit,f_auto,q_auto,w_320/https://varunmehta.com/img/2020/05/odroid-go.gif 320w, https://res.cloudinary.com/varun-blog/image/fetch/c_limit,f_auto,q_auto,w_480/https://varunmehta.com/img/2020/05/odroid-go.gif 480w&quot; sizes=&quot;(min-width: 50rem) 50rem, 90&quot; width=&quot;480&quot; height=&quot;297&quot; crossorigin=&quot;anonymous&quot; /&gt;&lt;/p&gt;

&lt;h4 id=&quot;hardware&quot;&gt;Hardware&lt;/h4&gt;

&lt;p&gt;I also like playing in the boundaries between software and hardware, so taking a system like the Odroid Go and building my own graphics capabilities is exciting. As luck would have it, the folks at Seeed Studio released an “Arduino Shield” that they call the &lt;a href=&quot;https://wiki.seeedstudio.com/Spartan-Edge-Accelerator-Board/&quot;&gt;Spartan Edge Accelerator&lt;/a&gt;. Seeed releases many dev kits, boards, and peripherals, but this one caught my attention.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://res.cloudinary.com/varun-blog/image/fetch/c_limit,f_auto,q_auto,w_800/https://varunmehta.com/img/spartan-edge-accelerator.jpg&quot; srcset=&quot;https://res.cloudinary.com/varun-blog/image/fetch/c_limit,f_auto,q_auto,w_320/https://varunmehta.com/img/spartan-edge-accelerator.jpg 320w, https://res.cloudinary.com/varun-blog/image/fetch/c_limit,f_auto,q_auto,w_640/https://varunmehta.com/img/spartan-edge-accelerator.jpg 640w, https://res.cloudinary.com/varun-blog/image/fetch/c_limit,f_auto,q_auto,w_960/https://varunmehta.com/img/spartan-edge-accelerator.jpg 960w, https://res.cloudinary.com/varun-blog/image/fetch/c_limit,f_auto,q_auto,w_1024/https://varunmehta.com/img/spartan-edge-accelerator.jpg 1024w&quot; sizes=&quot;(min-width: 50rem) 50rem, 90&quot; width=&quot;1024&quot; height=&quot;768&quot; crossorigin=&quot;anonymous&quot; /&gt;&lt;/p&gt;

&lt;p&gt;At a high level, it’s a board meant to be attached to an Arduino, or potentially to run standalone. It includes:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Xilinx Spartan 7 xc7s15 (12.8k logic cells, 360 kilobits block ram)&lt;/li&gt;
  &lt;li&gt;Espressif ESP32 “for wireless”&lt;/li&gt;
  &lt;li&gt;HDMI port (raw)&lt;/li&gt;
  &lt;li&gt;CSI/MIPI interface (raw)&lt;/li&gt;
  &lt;li&gt;MicroSD slot&lt;/li&gt;
  &lt;li&gt;ADC chip&lt;/li&gt;
  &lt;li&gt;Accelerometer and gyroscope&lt;/li&gt;
  &lt;li&gt;A few LEDs and push buttons&lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&quot;requirements&quot;&gt;Requirements&lt;/h4&gt;

&lt;p&gt;Hard requirements:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;No additional processors or PCBs&lt;/li&gt;
  &lt;li&gt;Teach myself verilog&lt;/li&gt;
  &lt;li&gt;480p video support&lt;/li&gt;
  &lt;li&gt;60fps video&lt;/li&gt;
  &lt;li&gt;Single screen-style NES background&lt;/li&gt;
  &lt;li&gt;NES-level sprite support&lt;/li&gt;
  &lt;li&gt;16-bit color&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Stretch Requirements:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;720p video support&lt;/li&gt;
  &lt;li&gt;Super Mario Bros 3-level background scrolling&lt;/li&gt;
  &lt;li&gt;Super Mario World-level layer support&lt;/li&gt;
  &lt;li&gt;SNES-level sprite support&lt;/li&gt;
  &lt;li&gt;24-bit “true” color&lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&quot;challenges&quot;&gt;Challenges&lt;/h4&gt;

&lt;p&gt;A few challenges already stand out to me on making any of this possible.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Inter-IC Communication:&lt;/strong&gt; The ESP32 and FPGA on the Spartan Edge Accelerator weren’t really meant to continuously talk to each other: they’re both supposed to communicate with an Arduino. In fact, the ESP32 is only supposed to be using i2c most of the time! To have the type of dynamic display that I want, all data updates need to be copied from the ESP32 to the FPGA during the VBLANK period. This is similar to how systems like the NES worked, where there is a CPU and separate PPU (Picture Processing Unit) and the systems do not share memory. Looking at the schematic, my best bet is likely the QSPI connection that the ESP32 can use to flash the bitstream to the FPGA upon startup. Even with that, I’ll need to find a way for the FPGA/GPU to signal to the ESP32/CPU that VBLANK has begun and data can now be copied from CPU memory into GPU memory. And then there’s also the challenge of just &lt;em&gt;how much&lt;/em&gt; data can be copied during the VBLANK period over a connection like that.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Memory Size:&lt;/strong&gt; The ESP32 will have plenty of RAM for our purposes, at least 512KB. However, the FPGA has no discrete memory chip, and so will depend on its block RAM, which is limited to 45KB. This is &lt;em&gt;much more&lt;/em&gt; memory than available to the NES, but it’s not quite up to the &lt;strong&gt;64 KB&lt;/strong&gt; that the SNES could take advantage of. I don’t yet know how far I’ll be able to get towards 16-bit graphics, especially given that we’re targeting much higher screen resolutions than the NES or SNES.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&quot;follow-along&quot;&gt;Follow along!&lt;/h4&gt;

&lt;p&gt;This should be a fun time! I’ll add more posts as I work out the rest of the systems. If you want livestreaming updates, you can check out my &lt;a href=&quot;https://www.youtube.com/channel/UC0V--Ek0C3I9ztkxQN6iQnw/&quot;&gt;YouTube Channel&lt;/a&gt;, where I livestream most of my work on this project. Also, I’ll be checking in all of my progress on &lt;a href=&quot;https://github.com/smartperson/spartan-edge-accelerator-graphical-system&quot;&gt;GitHub&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;— Varun&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Who’s Varun? I work in Product at &lt;a href=&quot;https://alice-app.com&quot;&gt;ALICE&lt;/a&gt;, and I was previously founder of an HR tech startup &lt;a href=&quot;http://disqovery.com&quot;&gt;Disqovery&lt;/a&gt;. I have worn many hats, and I like making things. I also like talking business. You can reach me at &lt;a href=&quot;mailto:me@varunmehta.com&quot;&gt;me@varunmehta.com&lt;/a&gt;, &lt;a href=&quot;https://fosstodon.org/@smartperson&quot;&gt;Mastodon&lt;/a&gt;, &lt;a href=&quot;https://github.com/smartperson&quot;&gt;Github&lt;/a&gt;, and &lt;a href=&quot;https://linkedin.com/in/varunkmehta&quot;&gt;LinkedIn&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
</description>
        <pubDate>Fri, 22 May 2020 00:00:00 -0400</pubDate>
        <link>https://varunmehta.com/technology,/fpga/2020/05/22/fpga-game-graphics-part-0.html</link>
        <guid isPermaLink="true">https://varunmehta.com/technology,/fpga/2020/05/22/fpga-game-graphics-part-0.html</guid>
        
        
        <category>Technology,</category>
        
        <category>FPGA</category>
        
      </item>
    
      <item>
        <title>Opinionated Behavior-Driven Development</title>
        <description>&lt;p&gt;After having a few conversations on this topic recently, I’ve decided to put down my thoughts on behavior-driven development (BDD): what it’s for, how to use it, and how &lt;em&gt;not&lt;/em&gt; to use it.&lt;/p&gt;

&lt;ul id=&quot;markdown-toc&quot;&gt;
  &lt;li&gt;&lt;a href=&quot;#behavior-driven-development&quot; id=&quot;markdown-toc-behavior-driven-development&quot;&gt;Behavior-driven development&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#my-context&quot; id=&quot;markdown-toc-my-context&quot;&gt;My context&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#bdd-flow&quot; id=&quot;markdown-toc-bdd-flow&quot;&gt;BDD flow&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#bdd-tips&quot; id=&quot;markdown-toc-bdd-tips&quot;&gt;BDD tips&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&quot;behavior-driven-development&quot;&gt;Behavior-driven development&lt;/h4&gt;

&lt;p&gt;There is a good overview of BDD on &lt;a href=&quot;https://www.toptal.com/freelance/your-boss-won-t-appreciate-tdd-try-bdd&quot;&gt;Toptal’s blog&lt;/a&gt;, especially as it compares with test-driven development. My perspective: behavioral testing uses 100% implementation-neutral descriptions of what a user does with your software. BDD is writing those tests before any code is written, and using those tests to gauge the implementation’s progress. When all of the behavioral tests pass, the feature is complete.&lt;/p&gt;

&lt;h4 id=&quot;my-context&quot;&gt;My context&lt;/h4&gt;

&lt;p&gt;Working on Disqovery is what exposed me to BDD and behavioral testing in the first place. Because of that, my opinions and experiences are based on:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Web and mobile web users&lt;/li&gt;
  &lt;li&gt;Ruby on Rails backend, angular.js frontend&lt;/li&gt;
  &lt;li&gt;Existing unit testing framework&lt;/li&gt;
  &lt;li&gt;Agile-style project management&lt;/li&gt;
  &lt;li&gt;Using &lt;a href=&quot;https://cucumber.io&quot;&gt;cucumber&lt;/a&gt; with &lt;a href=&quot;http://phantomjs.org&quot;&gt;phantomJS&lt;/a&gt; set up with &lt;a href=&quot;https://github.com/teamcapybara/capybara&quot;&gt;capybara&lt;/a&gt; via &lt;a href=&quot;https://github.com/teampoltergeist/poltergeist&quot;&gt;poltergeist&lt;/a&gt;. &lt;em&gt;Yes, these project names are all very amusing.&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I won’t explain how to get cucumber or similar systems working. There are plenty of great tutorials and videos that will explain it better than I could. As you could imagine, some setup is required to get all of these pieces working together, but the benefits are numerous:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Every test is written in plain English, so every team member and stakeholder can understand what is being tested.&lt;/li&gt;
  &lt;li&gt;Tests are run automatically while developers are working on their code. We can catch regressions before any changes even submitted for code review.&lt;/li&gt;
  &lt;li&gt;The headless browser system sees &lt;em&gt;exactly&lt;/em&gt; what the end user sees, including all javascript and css. This is end-to-end testing in its purest form.&lt;/li&gt;
  &lt;li&gt;Not only are any test failures reported, but screenshots are automatically generated to aid in debugging and development.&lt;/li&gt;
&lt;/ol&gt;

&lt;h4 id=&quot;bdd-flow&quot;&gt;BDD flow&lt;/h4&gt;

&lt;p&gt;I incorporated behavioral testing and BDD into a pretty standard PM workflow. This works easily with cucumber since it breaks tests down into “Features” which consist of multiple “Scenarios.”&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Perform customer research, including an understanding of customer challenges and needs.&lt;/li&gt;
  &lt;li&gt;Synthesize feature descriptions, use cases, requirements statements, and priorities. This is the start of your functional spec. Get it signed off by stakeholders.&lt;/li&gt;
  &lt;li&gt;Convert spec features to cucumber features, use cases to cucumber scenarios, and requirements to cucumber steps, as appropriate. Tag scenarios according to milestones.&lt;/li&gt;
  &lt;li&gt;Work with development team to get features created. Behavioral tests will help track progress, as well as catch regressions.&lt;/li&gt;
  &lt;li&gt;Reprioritize as needed during development. Moving a failing test to another milestone makes it easy to identify exactly which features and use cases are affected.&lt;/li&gt;
  &lt;li&gt;Go back to step 1 once you’ve shipped a milestone to the customer.&lt;/li&gt;
&lt;/ol&gt;

&lt;h4 id=&quot;bdd-tips&quot;&gt;BDD tips&lt;/h4&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Developers don’t write behavioral tests. You do.&lt;/strong&gt; Let developers write the code-centric tests. The level of customer understanding for behavioral testing &lt;em&gt;requires&lt;/em&gt; they be written by the PM and UX designers. However developers are stakeholders, and should be part of the conversation.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Involve your stakeholders early and often.&lt;/strong&gt; Cucumber’s tests are written in plain English; use that to your advantage. Involve your stakeholders in the feedback process early, to make sure every sentence of the test agrees with their assessment of what a user should experience.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Think of scenario steps at the most basic levels.&lt;/strong&gt; Steps in cucumber should include verbs, and I focus on them being the simple possible verbs. The user ‘sees’ something, ‘clicks’ or ‘taps’ a button, ‘drags’ a picture. In some cases maybe they even ‘hear’ a sound or ‘select’ a file from their computer.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Cucumber is not a way to validate 100s of different devices/browsers.&lt;/strong&gt; Cucumber, along with the tools I described above, is a way of validating behaviors during development, but it is limited to a few browser frameworks. Tools like &lt;a href=&quot;https://www.xamarin.com/test-cloud&quot;&gt;Xamarin’s Test Cloud&lt;/a&gt; may provide the diversity of devices you’re looking for, at the expense of test complexity. There may be a way of connecting a behavioral testing framework with Xamarin Test Cloud; I’m not sure.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Close development feedback loops as tightly as possible. Automate test runs.&lt;/strong&gt; I set up our Rails test environment so that every time a developer saved a source file, the relevant tests would automatically run and report their results onscreen. Within seconds, developers would have feedback on whether they fixed and issue, or made it worse. Tighter feedback loops can speed development.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you’ve never heard of BDD before, this post may have piqued your interest. If you’re considering it, I hope my experience and implementation will prove helpful. Tools like cucumber are so incredibly flexible, that having some opinion on the “right way” to use them is better than none. Completely disagreeing with me is just as valid as completely agreeing.&lt;/p&gt;

&lt;p&gt;— Varun&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Who’s Varun? I most recently was the founder of an HR tech startup, &lt;a href=&quot;http://disqovery.com&quot;&gt;Disqovery&lt;/a&gt;. I have worn many hats, and I like making things. I also like talking business. You can reach me at &lt;a href=&quot;mailto:me@varunmehta.com&quot;&gt;me@varunmehta.com&lt;/a&gt;, &lt;a href=&quot;https://fosstodon.org/@smartperson&quot;&gt;Mastodon&lt;/a&gt;, &lt;a href=&quot;https://github.com/smartperson&quot;&gt;Github&lt;/a&gt;, and &lt;a href=&quot;https://linkedin.com/in/varunkmehta&quot;&gt;LinkedIn&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
</description>
        <pubDate>Wed, 11 Oct 2017 00:00:00 -0400</pubDate>
        <link>https://varunmehta.com/technology/2017/10/11/opinionated-behavior-driven-development.html</link>
        <guid isPermaLink="true">https://varunmehta.com/technology/2017/10/11/opinionated-behavior-driven-development.html</guid>
        
        
        <category>Technology</category>
        
      </item>
    
      <item>
        <title>Reverse Engineering: Logitech Z680 LCD Replacement, Part 2</title>
        <description>&lt;p&gt;Timing is everything, especially in serial communications. I’ve run into two interesting timing challenges on this project that need to be solved, but they couldn’t be more different. Check out &lt;a href=&quot;/technology/2017/09/04/reverse-engineering-logitech-z680-lcd.html&quot;&gt;part 1&lt;/a&gt; first if you have no idea what’s going on.&lt;/p&gt;

&lt;ul id=&quot;markdown-toc&quot;&gt;
  &lt;li&gt;&lt;a href=&quot;#hooking-up-a-real-lcd&quot; id=&quot;markdown-toc-hooking-up-a-real-lcd&quot;&gt;Hooking up a real LCD&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#gibberish-problem&quot; id=&quot;markdown-toc-gibberish-problem&quot;&gt;Gibberish problem&lt;/a&gt;    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#gibberish-solution&quot; id=&quot;markdown-toc-gibberish-solution&quot;&gt;Gibberish solution&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#custom-characters-problem&quot; id=&quot;markdown-toc-custom-characters-problem&quot;&gt;Custom characters problem&lt;/a&gt;    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#how-it-works&quot; id=&quot;markdown-toc-how-it-works&quot;&gt;How it works&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#why-it-doesnt-work&quot; id=&quot;markdown-toc-why-it-doesnt-work&quot;&gt;Why it doesn’t work&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#solutions&quot; id=&quot;markdown-toc-solutions&quot;&gt;Solutions&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#next-steps--code&quot; id=&quot;markdown-toc-next-steps--code&quot;&gt;Next steps &amp;amp; code&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#whos-varun&quot; id=&quot;markdown-toc-whos-varun&quot;&gt;Who’s Varun?&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&quot;hooking-up-a-real-lcd&quot;&gt;Hooking up a real LCD&lt;/h4&gt;

&lt;p&gt;In part 1, we got the SPI data from the control pod, and used ANSI escape codes to recreate the display in a terminal window on my Mac. This is very cool, but not useful. I need to turn the Arduino into a translator that converts control pod serial data into parallel data for a replacement LCD, likely using the HD44780 protocol.&lt;/p&gt;

&lt;div class=&quot;mdl-grid&quot;&gt;

  
  &lt;div class=&quot;mdl-cell mdl-cell--4-col&quot;&gt;
  
    
    &lt;a href=&quot;/img/2017/09/character-lcd-crystal-fontz-0420.jpg&quot; data-lightbox=&quot;character-displays&quot; title=&quot;I keep this on-hand for random projects. The white text on blue backlight has a nice look.&quot;&gt;
      
        &lt;img src=&quot;https://res.cloudinary.com/varun-blog/image/fetch/c_limit,f_auto,q_auto,w_800/https://varunmehta.com/img/2017/09/character-lcd-crystal-fontz-0420.jpg&quot; srcset=&quot;https://res.cloudinary.com/varun-blog/image/fetch/c_limit,f_auto,q_auto,w_320/https://varunmehta.com/img/2017/09/character-lcd-crystal-fontz-0420.jpg 320w,
https://res.cloudinary.com/varun-blog/image/fetch/c_limit,f_auto,q_auto,w_640/https://varunmehta.com/img/2017/09/character-lcd-crystal-fontz-0420.jpg 640w,
https://res.cloudinary.com/varun-blog/image/fetch/c_limit,f_auto,q_auto,w_960/https://varunmehta.com/img/2017/09/character-lcd-crystal-fontz-0420.jpg 960w,
https://res.cloudinary.com/varun-blog/image/fetch/c_limit,f_auto,q_auto,w_1280/https://varunmehta.com/img/2017/09/character-lcd-crystal-fontz-0420.jpg 1280w,
https://res.cloudinary.com/varun-blog/image/fetch/c_limit,f_auto,q_auto,w_1600/https://varunmehta.com/img/2017/09/character-lcd-crystal-fontz-0420.jpg 1600w&quot; sizes=&quot;(min-width: 50rem) 50rem, 90&quot; width=&quot;3836&quot; height=&quot;1129&quot; crossorigin=&quot;anonymous&quot; /&gt;
      
    &lt;/a&gt;
  &lt;/div&gt;

  
  &lt;div class=&quot;mdl-cell mdl-cell--4-col&quot;&gt;
  
    
    &lt;a href=&quot;/img/2017/09/character-oled-new-haven-0220.jpg&quot; data-lightbox=&quot;character-displays&quot; title=&quot;I bought one of these for this project. The yellow on black reminds me of old home theater equipment.&quot;&gt;
      
        &lt;img src=&quot;https://res.cloudinary.com/varun-blog/image/fetch/c_limit,f_auto,q_auto,w_800/https://varunmehta.com/img/2017/09/character-oled-new-haven-0220.jpg&quot; srcset=&quot;https://res.cloudinary.com/varun-blog/image/fetch/c_limit,f_auto,q_auto,w_320/https://varunmehta.com/img/2017/09/character-oled-new-haven-0220.jpg 320w,
https://res.cloudinary.com/varun-blog/image/fetch/c_limit,f_auto,q_auto,w_500/https://varunmehta.com/img/2017/09/character-oled-new-haven-0220.jpg 500w&quot; sizes=&quot;(min-width: 50rem) 50rem, 90&quot; width=&quot;500&quot; height=&quot;193&quot; crossorigin=&quot;anonymous&quot; /&gt;
      
    &lt;/a&gt;
  &lt;/div&gt;

&lt;/div&gt;

&lt;p&gt;I ordered a slick, slim 2x20 &lt;a href=&quot;https://octopart.com/nhd-0220cw-ay3-newhaven+display-51374308&quot;&gt;character OLED&lt;/a&gt; for a replacement, but in the meanwhile, I have an old 4x20 &lt;a href=&quot;https://www.crystalfontz.com/product/cfah4004atmijt-character-display-40x4&quot;&gt;CrystalFontz character LCD&lt;/a&gt; I can use to test my code and hookups.&lt;/p&gt;

&lt;div class=&quot;mdl-grid&quot;&gt;

  
  &lt;div class=&quot;mdl-cell mdl-cell--4-col&quot;&gt;
  
    
    &lt;a href=&quot;/img/2017/09/z680-replacement-wiring.jpg&quot; data-lightbox=&quot;replacement-connections&quot; title=&quot;The control pod (left) is wired up to the Arduino&apos;s SPI pins(middle). My replacement LCD (right) is connected to other GPIO on the Arduino.&quot;&gt;
      
        &lt;img src=&quot;https://res.cloudinary.com/varun-blog/image/fetch/c_limit,f_auto,q_auto,w_800/https://varunmehta.com/img/2017/09/z680-replacement-wiring.jpg&quot; srcset=&quot;https://res.cloudinary.com/varun-blog/image/fetch/c_limit,f_auto,q_auto,w_320/https://varunmehta.com/img/2017/09/z680-replacement-wiring.jpg 320w,
https://res.cloudinary.com/varun-blog/image/fetch/c_limit,f_auto,q_auto,w_640/https://varunmehta.com/img/2017/09/z680-replacement-wiring.jpg 640w,
https://res.cloudinary.com/varun-blog/image/fetch/c_limit,f_auto,q_auto,w_960/https://varunmehta.com/img/2017/09/z680-replacement-wiring.jpg 960w,
https://res.cloudinary.com/varun-blog/image/fetch/c_limit,f_auto,q_auto,w_1280/https://varunmehta.com/img/2017/09/z680-replacement-wiring.jpg 1280w,
https://res.cloudinary.com/varun-blog/image/fetch/c_limit,f_auto,q_auto,w_1600/https://varunmehta.com/img/2017/09/z680-replacement-wiring.jpg 1600w&quot; sizes=&quot;(min-width: 50rem) 50rem, 90&quot; width=&quot;3228&quot; height=&quot;1194&quot; crossorigin=&quot;anonymous&quot; /&gt;
      
    &lt;/a&gt;
  &lt;/div&gt;

  
  &lt;div class=&quot;mdl-cell mdl-cell--4-col&quot;&gt;
  
    
    &lt;a href=&quot;/img/2017/09/z680-oled-connections.png&quot; data-lightbox=&quot;replacement-connections&quot; title=&quot;A simplified diagram that might make more sense. Everything is powered from the control pod, with the Arduino acting as a data intermediary.&quot;&gt;
      
        &lt;img src=&quot;https://res.cloudinary.com/varun-blog/image/fetch/c_limit,f_auto,q_auto,w_800/https://varunmehta.com/img/2017/09/z680-oled-connections.png&quot; srcset=&quot;https://res.cloudinary.com/varun-blog/image/fetch/c_limit,f_auto,q_auto,w_320/https://varunmehta.com/img/2017/09/z680-oled-connections.png 320w,
https://res.cloudinary.com/varun-blog/image/fetch/c_limit,f_auto,q_auto,w_640/https://varunmehta.com/img/2017/09/z680-oled-connections.png 640w,
https://res.cloudinary.com/varun-blog/image/fetch/c_limit,f_auto,q_auto,w_700/https://varunmehta.com/img/2017/09/z680-oled-connections.png 700w&quot; sizes=&quot;(min-width: 50rem) 50rem, 90&quot; width=&quot;700&quot; height=&quot;364&quot; crossorigin=&quot;anonymous&quot; /&gt;
      
    &lt;/a&gt;
  &lt;/div&gt;

&lt;/div&gt;

&lt;p&gt;It’s a parallel LCD, so we have 4 bits of data [D4:7], enable [E], and instruction/data register select [RS]. Since we’re starting with an HD44780-compatible display, I can simply use the Arduino &lt;a href=&quot;https://www.arduino.cc/en/Reference/LiquidCrystal/&quot;&gt;LiquidCrystal&lt;/a&gt; library. Most of the commands from the control pod map directly to hd44780 commands, so I can either parse the commands, or just send the raw payload directly to the display. Here’s an example from the &lt;a href=&quot;https://github.com/smartperson/Logitech-Z680-LCD-Translator/blob/master/logitech_z680_lcd_translate.ino#L76&quot;&gt;actual code&lt;/a&gt;.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-c&quot; data-lang=&quot;c&quot;&gt;    &lt;span class=&quot;c1&quot;&gt;//payload is the decoded byte from the 3-byte serial sequences the control pod sends&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;switch&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;payload&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x01&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;Serial&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;\e[2J&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;lcd&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;clear&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;//parse the command and send via library&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;break&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x02&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;Serial&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;\e[1;1f&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;lcd&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;home&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;//parse the command and send via library&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;break&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x40&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;Serial&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;\e[2J&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;lcd&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;command&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;payload&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;//send the raw command&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;break&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x0c&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;//turns on LCD, disables cursor&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;lcd&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;command&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;payload&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;//send the raw command&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;break&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;So basically the code works like this:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;The Arduino SPI interrupt adds any received bytes to the buffer.&lt;/li&gt;
  &lt;li&gt;The main code loop processes the buffer whenever there are a multiple of 3 bytes in the buffer. (Remember that all data from the control pod comes in 3-byte sequences).&lt;/li&gt;
  &lt;li&gt;For each 3-byte sequence, the main code loop performs the appropriate commands on the LCD.&lt;/li&gt;
  &lt;li&gt;Once finished, the main code loop clears the buffer and waits for more data.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This is a simple solution that should work really well, right?&lt;/p&gt;

&lt;h4 id=&quot;gibberish-problem&quot;&gt;Gibberish problem&lt;/h4&gt;

&lt;p&gt;No, the solution was not so simple.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://res.cloudinary.com/varun-blog/image/fetch/c_limit,f_auto,q_auto,w_800/https://varunmehta.com/img/2017/09/z680-problem-gibberish.jpg&quot; srcset=&quot;https://res.cloudinary.com/varun-blog/image/fetch/c_limit,f_auto,q_auto,w_320/https://varunmehta.com/img/2017/09/z680-problem-gibberish.jpg 320w, https://res.cloudinary.com/varun-blog/image/fetch/c_limit,f_auto,q_auto,w_640/https://varunmehta.com/img/2017/09/z680-problem-gibberish.jpg 640w, https://res.cloudinary.com/varun-blog/image/fetch/c_limit,f_auto,q_auto,w_960/https://varunmehta.com/img/2017/09/z680-problem-gibberish.jpg 960w, https://res.cloudinary.com/varun-blog/image/fetch/c_limit,f_auto,q_auto,w_1280/https://varunmehta.com/img/2017/09/z680-problem-gibberish.jpg 1280w, https://res.cloudinary.com/varun-blog/image/fetch/c_limit,f_auto,q_auto,w_1600/https://varunmehta.com/img/2017/09/z680-problem-gibberish.jpg 1600w&quot; sizes=&quot;(min-width: 50rem) 50rem, 90&quot; width=&quot;4032&quot; height=&quot;1779&quot; crossorigin=&quot;anonymous&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;It’s supposed to say “Input: Optical” and “Effect: Detecting”. You can kind of see that.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;This is a silly issue, but it drove me crazy for a little while. I did not really think about how the SPI interrupt and my main code loop interact. The interrupt can be called at any time, &lt;em&gt;including when the main code loop is performing commands on the LCD&lt;/em&gt;. If I’m not careful, the main code loop can clear out the buffer without processing all of the data, because its state can overwrite more recent information coming from the interrupt function. This results in missing data. This could be missing commands or missing letters, and results in the gibberish you see above. This is a classic &lt;a href=&quot;https://en.wikipedia.org/wiki/Producer–consumer_problem&quot;&gt;producer-consumer&lt;/a&gt; situation, and I can probably use a classic solution to get around it.&lt;/p&gt;

&lt;h5 id=&quot;gibberish-solution&quot;&gt;Gibberish solution&lt;/h5&gt;

&lt;p&gt;&lt;strong&gt;Ring buffers&lt;/strong&gt; are the best solution I can think of. I used &lt;a href=&quot;http://steve.kargs.net/software/interrupt-safe-ring-buffer-library/&quot;&gt;Steve Karg’s&lt;/a&gt; FIFO library for another project in the past, and fortunately he has an interrupt-friendly ring buffer available, as well. This library allows for objects of any arbitrary size, include our 3-byte sequences. As an added bonus, it’s lightweight, and it will use minimal space on our Arduino board.&lt;/p&gt;

&lt;p&gt;The ring buffer changes the code like this:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;The Arduino SPI interrupt adds any received 3-byte sequences to the ring buffer.&lt;/li&gt;
  &lt;li&gt;The main code loop pops an item off of the ring buffer and processes it.&lt;/li&gt;
  &lt;li&gt;When the main code loop is finished it goes back to step 2.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In this way, the main loop &lt;em&gt;is not responsible for clearing the buffer&lt;/em&gt;. This is enough to make sure no data is clobbered, and the SPI interrupt can safely add more data to the ring buffer any time it needs to.&lt;/p&gt;

&lt;div class=&quot;mdl-grid&quot;&gt;

  
  &lt;div class=&quot;mdl-cell mdl-cell--12-col&quot;&gt;
  
    
    &lt;a href=&quot;/img/2017/09/z680-oled-success-initial.jpg&quot; data-lightbox=&quot;z680-oled-success-initial&quot; title=&quot;Awesome, now we can actually get real content showing up on our own display.&quot;&gt;
      
        &lt;img src=&quot;https://res.cloudinary.com/varun-blog/image/fetch/c_limit,f_auto,q_auto,w_800/https://varunmehta.com/img/2017/09/z680-oled-success-initial.jpg&quot; srcset=&quot;https://res.cloudinary.com/varun-blog/image/fetch/c_limit,f_auto,q_auto,w_320/https://varunmehta.com/img/2017/09/z680-oled-success-initial.jpg 320w,
https://res.cloudinary.com/varun-blog/image/fetch/c_limit,f_auto,q_auto,w_640/https://varunmehta.com/img/2017/09/z680-oled-success-initial.jpg 640w,
https://res.cloudinary.com/varun-blog/image/fetch/c_limit,f_auto,q_auto,w_960/https://varunmehta.com/img/2017/09/z680-oled-success-initial.jpg 960w,
https://res.cloudinary.com/varun-blog/image/fetch/c_limit,f_auto,q_auto,w_1280/https://varunmehta.com/img/2017/09/z680-oled-success-initial.jpg 1280w,
https://res.cloudinary.com/varun-blog/image/fetch/c_limit,f_auto,q_auto,w_1600/https://varunmehta.com/img/2017/09/z680-oled-success-initial.jpg 1600w&quot; sizes=&quot;(min-width: 50rem) 50rem, 90&quot; width=&quot;4032&quot; height=&quot;1196&quot; crossorigin=&quot;anonymous&quot; /&gt;
      
    &lt;/a&gt;
  &lt;/div&gt;

&lt;/div&gt;

&lt;p&gt;Success! If only for some screens, we can now get data sent successfully from the control pod all the way to our own replacement display.&lt;/p&gt;

&lt;h4 id=&quot;custom-characters-problem&quot;&gt;Custom characters problem&lt;/h4&gt;

&lt;p&gt;If the Z680 control pod just used standard letters, numbers, and symbols we would be done. Unfortunately, this is not so. The display uses custom characters for volume control and a few other functions, and those aren’t working with our new display yet.&lt;/p&gt;

&lt;h5 id=&quot;how-it-works&quot;&gt;How it works&lt;/h5&gt;

&lt;p&gt;When the control pod is first powered up:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;It enters standby mode. Speakers are off. LCD backlight is off. LCD power is on.&lt;/li&gt;
  &lt;li&gt;Control pod initializes the LCD.&lt;/li&gt;
  &lt;li&gt;It sends all of the data necessary to define the custom characters.&lt;/li&gt;
  &lt;li&gt;It clears the LCD contents, and keeps the backlight off.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Only when the power button is pressed is control pod is turned on. Then the speakers turn on, the backlight turns on, and content is sent to the LCD for display. The LCD uses a 5x8 font, and supports up to 8 custom characters. Each custom character is defined using the lower 5-bits of an 8-byte sequence. There’s a nice breakdown of this &lt;a href=&quot;http://www.dinceraydin.com/lcd/custom.htm&quot;&gt;here&lt;/a&gt; if you would like more information. I captured all of the data with my logic analyzer, so this breakdown might be helpful:&lt;/p&gt;

&lt;div class=&quot;mdl-grid&quot;&gt;

  
  &lt;div class=&quot;mdl-cell mdl-cell--4-col&quot;&gt;
  
    
    &lt;a href=&quot;/img/2017/09/z680-custom-pixels.png&quot; data-lightbox=&quot;z680-custom-characters&quot; title=&quot;All the custom characters and their purposes.&quot;&gt;
      
        &lt;img src=&quot;https://res.cloudinary.com/varun-blog/image/fetch/c_limit,f_auto,q_auto,w_800/https://varunmehta.com/img/2017/09/z680-custom-pixels.png&quot; srcset=&quot;https://res.cloudinary.com/varun-blog/image/fetch/c_limit,f_auto,q_auto,w_320/https://varunmehta.com/img/2017/09/z680-custom-pixels.png 320w,
https://res.cloudinary.com/varun-blog/image/fetch/c_limit,f_auto,q_auto,w_640/https://varunmehta.com/img/2017/09/z680-custom-pixels.png 640w,
https://res.cloudinary.com/varun-blog/image/fetch/c_limit,f_auto,q_auto,w_960/https://varunmehta.com/img/2017/09/z680-custom-pixels.png 960w,
https://res.cloudinary.com/varun-blog/image/fetch/c_limit,f_auto,q_auto,w_1119/https://varunmehta.com/img/2017/09/z680-custom-pixels.png 1119w&quot; sizes=&quot;(min-width: 50rem) 50rem, 90&quot; width=&quot;1119&quot; height=&quot;641&quot; crossorigin=&quot;anonymous&quot; /&gt;
      
    &lt;/a&gt;
  &lt;/div&gt;

  
  &lt;div class=&quot;mdl-cell mdl-cell--4-col&quot;&gt;
  
    
    &lt;a href=&quot;/img/2017/09/z680-custom-in-action-1.jpg&quot; data-lightbox=&quot;z680-custom-characters&quot; title=&quot;You can see the Dolby logo characters here.&quot;&gt;
      
        &lt;img src=&quot;https://res.cloudinary.com/varun-blog/image/fetch/c_limit,f_auto,q_auto,w_800/https://varunmehta.com/img/2017/09/z680-custom-in-action-1.jpg&quot; srcset=&quot;https://res.cloudinary.com/varun-blog/image/fetch/c_limit,f_auto,q_auto,w_320/https://varunmehta.com/img/2017/09/z680-custom-in-action-1.jpg 320w,
https://res.cloudinary.com/varun-blog/image/fetch/c_limit,f_auto,q_auto,w_640/https://varunmehta.com/img/2017/09/z680-custom-in-action-1.jpg 640w,
https://res.cloudinary.com/varun-blog/image/fetch/c_limit,f_auto,q_auto,w_960/https://varunmehta.com/img/2017/09/z680-custom-in-action-1.jpg 960w,
https://res.cloudinary.com/varun-blog/image/fetch/c_limit,f_auto,q_auto,w_1280/https://varunmehta.com/img/2017/09/z680-custom-in-action-1.jpg 1280w,
https://res.cloudinary.com/varun-blog/image/fetch/c_limit,f_auto,q_auto,w_1600/https://varunmehta.com/img/2017/09/z680-custom-in-action-1.jpg 1600w&quot; sizes=&quot;(min-width: 50rem) 50rem, 90&quot; width=&quot;2843&quot; height=&quot;1094&quot; crossorigin=&quot;anonymous&quot; /&gt;
      
    &lt;/a&gt;
  &lt;/div&gt;

  
  &lt;div class=&quot;mdl-cell mdl-cell--4-col&quot;&gt;
  
    
    &lt;a href=&quot;/img/2017/09/z680-custom-in-action-2.jpg&quot; data-lightbox=&quot;z680-custom-characters&quot; title=&quot;Here is the cursor when the surround settings are balanced.&quot;&gt;
      
        &lt;img src=&quot;https://res.cloudinary.com/varun-blog/image/fetch/c_limit,f_auto,q_auto,w_800/https://varunmehta.com/img/2017/09/z680-custom-in-action-2.jpg&quot; srcset=&quot;https://res.cloudinary.com/varun-blog/image/fetch/c_limit,f_auto,q_auto,w_320/https://varunmehta.com/img/2017/09/z680-custom-in-action-2.jpg 320w,
https://res.cloudinary.com/varun-blog/image/fetch/c_limit,f_auto,q_auto,w_640/https://varunmehta.com/img/2017/09/z680-custom-in-action-2.jpg 640w,
https://res.cloudinary.com/varun-blog/image/fetch/c_limit,f_auto,q_auto,w_960/https://varunmehta.com/img/2017/09/z680-custom-in-action-2.jpg 960w,
https://res.cloudinary.com/varun-blog/image/fetch/c_limit,f_auto,q_auto,w_1280/https://varunmehta.com/img/2017/09/z680-custom-in-action-2.jpg 1280w,
https://res.cloudinary.com/varun-blog/image/fetch/c_limit,f_auto,q_auto,w_1600/https://varunmehta.com/img/2017/09/z680-custom-in-action-2.jpg 1600w&quot; sizes=&quot;(min-width: 50rem) 50rem, 90&quot; width=&quot;2990&quot; height=&quot;1030&quot; crossorigin=&quot;anonymous&quot; /&gt;
      
    &lt;/a&gt;
  &lt;/div&gt;

  
  &lt;div class=&quot;mdl-cell mdl-cell--4-col&quot;&gt;
  
    
    &lt;a href=&quot;/img/2017/09/z680-custom-in-action-3.gif&quot; data-lightbox=&quot;z680-custom-characters&quot; title=&quot;Here is the volume cursor in action. It uses 4 different custom characters.&quot;&gt;
      
        &lt;img src=&quot;https://res.cloudinary.com/varun-blog/image/fetch/c_limit,f_auto,q_auto,w_800/https://varunmehta.com/img/2017/09/z680-custom-in-action-3.gif&quot; srcset=&quot;https://res.cloudinary.com/varun-blog/image/fetch/c_limit,f_auto,q_auto,w_320/https://varunmehta.com/img/2017/09/z680-custom-in-action-3.gif 320w,
https://res.cloudinary.com/varun-blog/image/fetch/c_limit,f_auto,q_auto,w_339/https://varunmehta.com/img/2017/09/z680-custom-in-action-3.gif 339w&quot; sizes=&quot;(min-width: 50rem) 50rem, 90&quot; width=&quot;339&quot; height=&quot;75&quot; crossorigin=&quot;anonymous&quot; /&gt;
      
    &lt;/a&gt;
  &lt;/div&gt;

&lt;/div&gt;

&lt;h5 id=&quot;why-it-doesnt-work&quot;&gt;Why it doesn’t work&lt;/h5&gt;

&lt;p&gt;Like the gibberish problem, getting custom characters working also comes down to timing. The Arduino and OLED are both powered by the control pod, and the pod sends custom characters into CGRAM very quickly after it is plugged in and goes into standby mode. &lt;strong&gt;This is the only time it sends custom characters.&lt;/strong&gt; We need to catch it quickly if we want to read that data, and unfortunately Arduino systems can take a long time to start running code after power on.&lt;/p&gt;

&lt;div class=&quot;mdl-grid&quot;&gt;

  
  &lt;div class=&quot;mdl-cell mdl-cell--4-col&quot;&gt;
  
    
    &lt;a href=&quot;/img/2017/09/z680-power-slave-select.png&quot; data-lightbox=&quot;z680-oscope-measurements&quot; title=&quot;We have about 125ms before the control pod sends data.&quot;&gt;
      
        &lt;img src=&quot;https://res.cloudinary.com/varun-blog/image/fetch/c_limit,f_auto,q_auto,w_800/https://varunmehta.com/img/2017/09/z680-power-slave-select.png&quot; srcset=&quot;https://res.cloudinary.com/varun-blog/image/fetch/c_limit,f_auto,q_auto,w_320/https://varunmehta.com/img/2017/09/z680-power-slave-select.png 320w,
https://res.cloudinary.com/varun-blog/image/fetch/c_limit,f_auto,q_auto,w_640/https://varunmehta.com/img/2017/09/z680-power-slave-select.png 640w,
https://res.cloudinary.com/varun-blog/image/fetch/c_limit,f_auto,q_auto,w_960/https://varunmehta.com/img/2017/09/z680-power-slave-select.png 960w,
https://res.cloudinary.com/varun-blog/image/fetch/c_limit,f_auto,q_auto,w_1280/https://varunmehta.com/img/2017/09/z680-power-slave-select.png 1280w,
https://res.cloudinary.com/varun-blog/image/fetch/c_limit,f_auto,q_auto,w_1330/https://varunmehta.com/img/2017/09/z680-power-slave-select.png 1330w&quot; sizes=&quot;(min-width: 50rem) 50rem, 90&quot; width=&quot;1330&quot; height=&quot;680&quot; crossorigin=&quot;anonymous&quot; /&gt;
      
    &lt;/a&gt;
  &lt;/div&gt;

  
  &lt;div class=&quot;mdl-cell mdl-cell--4-col&quot;&gt;
  
    
    &lt;a href=&quot;/img/2017/09/z680-arduino-poweron-ready-0-vanilla.png&quot; data-lightbox=&quot;z680-oscope-measurements&quot; title=&quot;It takes 1.5 seconds for the Arduino to get ready.&quot;&gt;
      
        &lt;img src=&quot;https://res.cloudinary.com/varun-blog/image/fetch/c_limit,f_auto,q_auto,w_800/https://varunmehta.com/img/2017/09/z680-arduino-poweron-ready-0-vanilla.png&quot; srcset=&quot;https://res.cloudinary.com/varun-blog/image/fetch/c_limit,f_auto,q_auto,w_320/https://varunmehta.com/img/2017/09/z680-arduino-poweron-ready-0-vanilla.png 320w,
https://res.cloudinary.com/varun-blog/image/fetch/c_limit,f_auto,q_auto,w_640/https://varunmehta.com/img/2017/09/z680-arduino-poweron-ready-0-vanilla.png 640w,
https://res.cloudinary.com/varun-blog/image/fetch/c_limit,f_auto,q_auto,w_960/https://varunmehta.com/img/2017/09/z680-arduino-poweron-ready-0-vanilla.png 960w,
https://res.cloudinary.com/varun-blog/image/fetch/c_limit,f_auto,q_auto,w_1280/https://varunmehta.com/img/2017/09/z680-arduino-poweron-ready-0-vanilla.png 1280w,
https://res.cloudinary.com/varun-blog/image/fetch/c_limit,f_auto,q_auto,w_1330/https://varunmehta.com/img/2017/09/z680-arduino-poweron-ready-0-vanilla.png 1330w&quot; sizes=&quot;(min-width: 50rem) 50rem, 90&quot; width=&quot;1330&quot; height=&quot;680&quot; crossorigin=&quot;anonymous&quot; /&gt;
      
    &lt;/a&gt;
  &lt;/div&gt;

&lt;/div&gt;

&lt;p&gt;My oscilloscope shows we have about 125ms after power before the first bye of data is sent by the control pod. To see how long it takes Arduino to start up, I wrote a simple app that just sets a pin high. &lt;em&gt;The RBBB Arduino starts after 1500ms!&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why am I using an oscilloscope instead of logic analyzer?&lt;/strong&gt; Since I have to measure the 5V voltage rail, and Arduino expects at least 4.5V before it turns on, I need to see the analog values. If I used a digital logic analyzer, it might report every value above 3.0V as “logical 1”, which would make understanding the power behavior harder. Additionally, this way I can check for voltage dips during boot.&lt;/p&gt;

&lt;h5 id=&quot;solutions&quot;&gt;Solutions&lt;/h5&gt;

&lt;p&gt;The most obvious solution would be to hardcode the custom character information into my Arduino. &lt;em&gt;But that would not be a ‘clean’ solution.&lt;/em&gt; I would never expect the control pod to send different custom characters, but I don’t care; if I’m solving this problem, I’m going to solve it right.&lt;/p&gt;

&lt;p&gt;In that case, the only other easy solutions are:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Replace the default Arduino bootloader with a faster one, like &lt;a href=&quot;https://learn.adafruit.com/arduino-tips-tricks-and-techniques/bootloader&quot;&gt;adaboot&lt;/a&gt; or &lt;a href=&quot;https://github.com/Optiboot/optiboot&quot;&gt;optiboot&lt;/a&gt;.&lt;/li&gt;
  &lt;li&gt;Reprogram the fuses on the ATmega328p on the board to start execution faster. The default is 65ms after power is applied, and it can be dangerous to execute sooner.&lt;/li&gt;
  &lt;li&gt;Bypass the power stabilization components on the RBBB so that we can feed the ATmega328p with 5V sooner.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The bootloader seems like the safest option, and the one with the most potential gains. Lucky me, I still own an Atmel STK500. It is the AVR programmer that all other AVR programmers are based on, and it is exceptionally powerful, if a little dated.&lt;/p&gt;

&lt;div class=&quot;mdl-grid&quot;&gt;

  
  &lt;div class=&quot;mdl-cell mdl-cell--4-col&quot;&gt;
  
    
    &lt;a href=&quot;/img/2017/09/stk500-connected-system.jpg&quot; data-lightbox=&quot;stk500-connected&quot; title=&quot;I don&apos;t have a spare 10-15V power brick, so the STK500 is getting *very* clean power from my bench supply.&quot;&gt;
      
        &lt;img src=&quot;https://res.cloudinary.com/varun-blog/image/fetch/c_limit,f_auto,q_auto,w_800/https://varunmehta.com/img/2017/09/stk500-connected-system.jpg&quot; srcset=&quot;https://res.cloudinary.com/varun-blog/image/fetch/c_limit,f_auto,q_auto,w_320/https://varunmehta.com/img/2017/09/stk500-connected-system.jpg 320w,
https://res.cloudinary.com/varun-blog/image/fetch/c_limit,f_auto,q_auto,w_640/https://varunmehta.com/img/2017/09/stk500-connected-system.jpg 640w,
https://res.cloudinary.com/varun-blog/image/fetch/c_limit,f_auto,q_auto,w_960/https://varunmehta.com/img/2017/09/stk500-connected-system.jpg 960w,
https://res.cloudinary.com/varun-blog/image/fetch/c_limit,f_auto,q_auto,w_1280/https://varunmehta.com/img/2017/09/stk500-connected-system.jpg 1280w,
https://res.cloudinary.com/varun-blog/image/fetch/c_limit,f_auto,q_auto,w_1600/https://varunmehta.com/img/2017/09/stk500-connected-system.jpg 1600w&quot; sizes=&quot;(min-width: 50rem) 50rem, 90&quot; width=&quot;4032&quot; height=&quot;3024&quot; crossorigin=&quot;anonymous&quot; /&gt;
      
    &lt;/a&gt;
  &lt;/div&gt;

  
  &lt;div class=&quot;mdl-cell mdl-cell--4-col&quot;&gt;
  
    
    &lt;a href=&quot;/img/2017/09/stk500-connected-closeup.jpg&quot; data-lightbox=&quot;stk500-connected&quot; title=&quot;This is my STK500 configured for high-voltage parallel programming. Yes, it&apos;s a mess of wires and you need to read th e manual closely to do this right.&quot;&gt;
      
        &lt;img src=&quot;https://res.cloudinary.com/varun-blog/image/fetch/c_limit,f_auto,q_auto,w_800/https://varunmehta.com/img/2017/09/stk500-connected-closeup.jpg&quot; srcset=&quot;https://res.cloudinary.com/varun-blog/image/fetch/c_limit,f_auto,q_auto,w_320/https://varunmehta.com/img/2017/09/stk500-connected-closeup.jpg 320w,
https://res.cloudinary.com/varun-blog/image/fetch/c_limit,f_auto,q_auto,w_640/https://varunmehta.com/img/2017/09/stk500-connected-closeup.jpg 640w,
https://res.cloudinary.com/varun-blog/image/fetch/c_limit,f_auto,q_auto,w_960/https://varunmehta.com/img/2017/09/stk500-connected-closeup.jpg 960w,
https://res.cloudinary.com/varun-blog/image/fetch/c_limit,f_auto,q_auto,w_1280/https://varunmehta.com/img/2017/09/stk500-connected-closeup.jpg 1280w,
https://res.cloudinary.com/varun-blog/image/fetch/c_limit,f_auto,q_auto,w_1600/https://varunmehta.com/img/2017/09/stk500-connected-closeup.jpg 1600w&quot; sizes=&quot;(min-width: 50rem) 50rem, 90&quot; width=&quot;4032&quot; height=&quot;3024&quot; crossorigin=&quot;anonymous&quot; /&gt;
      
    &lt;/a&gt;
  &lt;/div&gt;

&lt;/div&gt;

&lt;p&gt;Getting my STK500 to flash the bootloader was unpleasant. I’ll spare the details of each step. Contact me if you want to discuss any of them:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Realize STK500 is so old it only supports v1 of the protocol, and can only be upgraded in Windows.&lt;/li&gt;
  &lt;li&gt;Install Atmel Studio on Windows. Every time you try to upgrade STK500’s firmware, Atmel Studio crashes.&lt;/li&gt;
  &lt;li&gt;Identify missing VC90 dll version and install.&lt;/li&gt;
  &lt;li&gt;Upgrade STK500 firmware.&lt;/li&gt;
  &lt;li&gt;Realize RS232 USB adapter kernel panics macOS 10.12.&lt;/li&gt;
  &lt;li&gt;Install Arduino tools on Ubuntu.&lt;/li&gt;
  &lt;li&gt;Edit Arduino system config so it supports STK500 high-voltage parallel programming.&lt;/li&gt;
  &lt;li&gt;Install optiboot in Arduino app.&lt;/li&gt;
  &lt;li&gt;Prepare STK500 for high-voltage parallel programming. Insert ATmega328p into STK500 slot 3.&lt;/li&gt;
  &lt;li&gt;Burn optiboot bootloader onto chip, and put chip back into Arduino board.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;img src=&quot;https://res.cloudinary.com/varun-blog/image/fetch/c_limit,f_auto,q_auto,w_800/https://varunmehta.com/img/2017/09/z680-arduino-poweron-ready-1-optiboot.png&quot; srcset=&quot;https://res.cloudinary.com/varun-blog/image/fetch/c_limit,f_auto,q_auto,w_320/https://varunmehta.com/img/2017/09/z680-arduino-poweron-ready-1-optiboot.png 320w, https://res.cloudinary.com/varun-blog/image/fetch/c_limit,f_auto,q_auto,w_640/https://varunmehta.com/img/2017/09/z680-arduino-poweron-ready-1-optiboot.png 640w, https://res.cloudinary.com/varun-blog/image/fetch/c_limit,f_auto,q_auto,w_960/https://varunmehta.com/img/2017/09/z680-arduino-poweron-ready-1-optiboot.png 960w, https://res.cloudinary.com/varun-blog/image/fetch/c_limit,f_auto,q_auto,w_1280/https://varunmehta.com/img/2017/09/z680-arduino-poweron-ready-1-optiboot.png 1280w, https://res.cloudinary.com/varun-blog/image/fetch/c_limit,f_auto,q_auto,w_1330/https://varunmehta.com/img/2017/09/z680-arduino-poweron-ready-1-optiboot.png 1330w&quot; sizes=&quot;(min-width: 50rem) 50rem, 90&quot; width=&quot;1330&quot; height=&quot;362&quot; crossorigin=&quot;anonymous&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Amazing, just flashing optiboot reduced startup time from 1.5s to 65 milliseconds!&lt;/strong&gt; Before trying anything else, I put my normal code back on the Arduino, and yes! The system starts up quickly enough to receive all of the CGRAM data from the control pod! Everything works! That’s how I took the yellow OLED pictures you saw earlier in the blog post.&lt;/p&gt;

&lt;h4 id=&quot;next-steps--code&quot;&gt;Next steps &amp;amp; code&lt;/h4&gt;
&lt;p&gt;Code for this project is available as a &lt;a href=&quot;https://github.com/smartperson/Logitech-Z680-LCD-Translator&quot;&gt;GitHub repository&lt;/a&gt;. I created my own version of LiquidCrystal that is designed for the NHD Character OLEDs instead. It’s very messy and not ready for primetime. When it is, I’ll spin it off into a proper Arduino library with its own github repo.&lt;/p&gt;

&lt;p&gt;The next step is figuring out exactly how to cram all of these electronics into the speaker control pod, in a way that works reliably and looks good. Maybe we can also get some bonus functionality. The character OLED datasheet has some neat features.&lt;/p&gt;

&lt;h4 id=&quot;whos-varun&quot;&gt;Who’s Varun?&lt;/h4&gt;

&lt;p&gt;&lt;em&gt;I most recently was the founder of an HR tech startup, &lt;a href=&quot;http://disqovery.com&quot;&gt;Disqovery&lt;/a&gt;. I have worn many hats, and I like making things. I also like talking business. You can reach me at &lt;a href=&quot;mailto:me@varunmehta.com&quot;&gt;me@varunmehta.com&lt;/a&gt;, &lt;a href=&quot;https://fosstodon.org/@smartperson&quot;&gt;Mastodon&lt;/a&gt;, &lt;a href=&quot;https://github.com/smartperson&quot;&gt;Github&lt;/a&gt;, and &lt;a href=&quot;https://linkedin.com/in/varunkmehta&quot;&gt;LinkedIn&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
</description>
        <pubDate>Sun, 24 Sep 2017 00:00:00 -0400</pubDate>
        <link>https://varunmehta.com/technology/2017/09/24/reverse-engineering-logitech-z680-lcd-2.html</link>
        <guid isPermaLink="true">https://varunmehta.com/technology/2017/09/24/reverse-engineering-logitech-z680-lcd-2.html</guid>
        
        
        <category>Technology</category>
        
      </item>
    
  </channel>
</rss>
