<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>Justin Overfelt</title>
    <link>https://jbo.io</link>
    <description>Justin Overfelt&#39;s Blog</description>
    <pubDate>Thu, 03 Jul 2025 13:59:59 -0400</pubDate>
    <item>
      <title>Iterator Adapters in Go</title>
      <link>https://jbo.io/iterator-adapters.html</link>
      <description>&lt;h1&gt;Iterator Adapters in Go&lt;/h1&gt;&#xA;&#xA;&lt;p&gt;With the release of version 1.23, Go now has support for what they call &amp;ldquo;range-over-func&amp;rdquo;, more commonly known as iterators. Essentially, it allows a developer to pass a function to for-range loops. This post won&amp;rsquo;t be about the basics of iterators in Go, as the basics are covered ably by Ian Lance Taylor on the &lt;a href=&#34;https://go.dev/blog/range-functions&#34;&gt;official Go blog&lt;/a&gt;.&lt;/p&gt;&#xA;&#xA;&lt;p&gt;Most of the posts I&amp;rsquo;ve seen with real-world examples of iterators focus on their use when paired with custom, generic collection types. To be sure, iterators are huge improvement in the ergonomics of custom collection types in Go. However, I&amp;rsquo;ve used them much more often in the context of what I&amp;rsquo;ll call &amp;ldquo;iterator adapters&amp;rdquo;. Because Go did not have first-class support for this concept prior to 1.23, there are a number of bespoke iterator implementations in the standard library. Common ones include iterating over CSV rows using encoding/csv, fetching entries in a tar file using archive/tar, and processing chunks of a multipart message using mime/multipart.&lt;/p&gt;&#xA;&#xA;&lt;p&gt;Typically, usage of these bespoke iterators involves an unconditional loop with clauses that break out of the loop upon receiving &lt;code&gt;io.EOF&lt;/code&gt;. The classical example with encoding/csv is below:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code&gt;// ...&#xA;cr := csv.NewReader(r)&#xA;&#xA;for {&#xA;    rec, err := r.Read()&#xA;&#xA;    if err == io.EOF {&#xA;        break&#xA;    }&#xA;&#xA;    if err != nil {&#xA;        log.Fatal(err)&#xA;    }&#xA;&#xA;    fmt.Println(record)&#xA;}&#xA;// ...&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&#xA;&lt;p&gt;This is rather annoying if the logic is any more complex than the simple example above. It forces you to mix iteration control with your business logic. Let&amp;rsquo;s look at how we might clean this up with an iterator adapter.&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code&gt;func allRecords(cr *csv.Reader) iter.Seq2[[]string, error] {&#xA;&#x9;return func(yield func([]string, error) bool) {&#xA;&#x9;&#x9;for {&#xA;&#x9;&#x9;&#x9;rec, err := r.Read()&#xA;&#x9;&#x9;&#x9;if err == io.EOF {&#xA;&#x9;&#x9;&#x9;&#x9;return&#xA;&#x9;&#x9;&#x9;}&#xA;&#xA;&#x9;&#x9;&#x9;if !yield(rec, err) {&#xA;&#x9;&#x9;&#x9;&#x9;return&#xA;&#x9;&#x9;&#x9;}&#xA;&#x9;&#x9;}&#xA;&#x9;}&#xA;}&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&#xA;&lt;p&gt;Now, we can use this new function to significantly clean up our code:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code&gt;// ...&#xA;cr := csv.NewReader(r)&#xA;&#xA;for rec, err := range allRecords(cr) {&#xA;&#x9;if err != nil {&#xA;&#x9;&#x9;log.Fatal(err)&#xA;&#x9;}&#xA;&#xA;&#x9;fmt.Println(rec)&#xA;}&#xA;// ...&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&#xA;&lt;p&gt;Not only is the business logic now separate from the iteration, it also reads better. Anyone looking at this code will immediately know that you&amp;rsquo;re looping over all records in a CSV file: &amp;ldquo;range over allRecords&amp;rdquo;. However, there are other benefits too. What if we decide that we don&amp;rsquo;t want the header of the CSV file? No problem, we don&amp;rsquo;t have to modify our application code at all - only the iterator adapter needs to be changed.&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code&gt;func allRecords(cr *csv.Reader) iter.Seq2[[]string, error] {&#xA;&#x9;return func(yield func([]string, error) bool) {&#xA;&#x9;&#x9;// read off the header&#xA;&#x9;&#x9;// any errors will be caught by the subsequent call to r.Read()&#xA;&#x9;&#x9;_, _ = r.Read()&#xA;&#xA;&#x9;&#x9;for {&#xA;&#x9;&#x9;&#x9;rec, err := r.Read()&#xA;&#x9;&#x9;&#x9;if err == io.EOF {&#xA;&#x9;&#x9;&#x9;&#x9;return&#xA;&#x9;&#x9;&#x9;}&#xA;&#xA;&#x9;&#x9;&#x9;if !yield(rec, err) {&#xA;&#x9;&#x9;&#x9;&#x9;return&#xA;&#x9;&#x9;&#x9;}&#xA;&#x9;&#x9;}&#xA;&#x9;}&#xA;}&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&#xA;&lt;p&gt;Done! You could even have two functions, &lt;code&gt;allRecords&lt;/code&gt; with the original behavior and &lt;code&gt;allRecordsNoHeader&lt;/code&gt; with the updated code. You can apply this pattern anytime you see an unconditional loop with a call to &lt;code&gt;Next()&lt;/code&gt; or a similar function. Perhaps the Go team will ultimately modify the standard library to include iterator adapters like this. However, you may want to handle errors differently or make subtle changes like our no-header CSV change. These adapters are so simple that it might make sense to allow each programmer to define them as they desire. Enjoy!&lt;/p&gt;&#xA;</description>
      <guid>https://jbo.io/iterator-adapters.html</guid>
      <pubDate>Thu, 03 Jul 2025 13:51:41 -0400</pubDate>
    </item>
    <item>
      <title>Open Source In Amateur Radio</title>
      <link>https://jbo.io/oss-ham-radio.html</link>
      <description>&lt;h1&gt;Open Source in Amateur Radio&lt;/h1&gt;&#xA;&#xA;&lt;p&gt;When I first got my Technician license in 2019, I heard people call amateur radio &amp;ldquo;the hobby of experimentation&amp;rdquo;. I was told I had received a &amp;ldquo;license to learn&amp;rdquo;. Indeed, 47 CFR Part 97, the section of the Code of Federal Regulations that governs amateur radio in the United States says this in Subpart A under &amp;ldquo;Basis And Purpose&amp;rdquo;:&lt;/p&gt;&#xA;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;The rules and regulations in this part are designed to provide an amateur radio service having a fundamental purpose as expressed in the following principles:&lt;/p&gt;&#xA;&#xA;&lt;p&gt;&amp;hellip;&lt;/p&gt;&#xA;&#xA;&lt;p&gt;Continuation and extension of the amateur&amp;rsquo;s proven ability to contribute to the advancement of the radio art.&lt;/p&gt;&#xA;&#xA;&lt;p&gt;Encouragement and improvement of the amateur service through rules which provide for advancing skills in both the communication and technical phases of the art.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&#xA;&lt;p&gt;I have found a great many fellow hams that live these principles in one way or another. One thing I was surprised to find though is that, as a community, we don&amp;rsquo;t uphold them when it comes to software. The vast majority of the software ever written for the amateur community is proprietary and Windows-only. Worse, it is Windows &lt;em&gt;desktop&lt;/em&gt; only, with little to no consideration given to mobile or remote operation.&lt;/p&gt;&#xA;&#xA;&lt;h2&gt;Yeah, but most computers run Windows&lt;/h2&gt;&#xA;&#xA;&lt;p&gt;This was once true, and probably is still true if all you&amp;rsquo;re counting are desktops and laptops. However, smartphones are by far the most popular computing devices these days, and none currently on sale run Windows. In fact, today we enjoy a huge diversity of computing devices and processor architectures. You can run Linux on a RISC-V processor, Windows on an ARM processor, NetBSD on x64, and on and on. This is a very different state of affairs than the Windows-on-Intel-processors status quo that existed when amateur radio software gained popularity. The Raspberry Pi brought low cost ARM devices that ran Linux to the masses, and the Pis and their clones are still hugely popular among hams. In fact, many of the new entrants to the hobby come from the tech world now (I&amp;rsquo;m one of them!). They might want to run higher power in the 2.4GHz band, or maybe they played with Meshtastic and are looking for more. These people expect a thriving open source culture from the hobby of experimentation.&lt;/p&gt;&#xA;&#xA;&lt;p&gt;Perhaps you don&amp;rsquo;t come from this culture, and are wondering what all the fuss is about.&lt;/p&gt;&#xA;&#xA;&lt;h2&gt;Why Open Source?&lt;/h2&gt;&#xA;&#xA;&lt;p&gt;Many reasons are detailed elsewhere, ask your favorite LLM! I&amp;rsquo;ll focus on the reasons that are specific to amateur radio.&lt;/p&gt;&#xA;&#xA;&lt;h3&gt;Be an experimenter&lt;/h3&gt;&#xA;&#xA;&lt;p&gt;Open Source, and specifically Free Software, &lt;a href=&#34;https://www.gnu.org/philosophy/free-sw.html&#34;&gt;preserves your freedom&lt;/a&gt;. In amateur radio, it also preserves your ability to experiment. You can&amp;rsquo;t experiment and learn from closed source software. It turns you into the dreaded appliance operator. You can&amp;rsquo;t mold your setup the way you want it. You can&amp;rsquo;t support any configuration the software author hasn&amp;rsquo;t previously thought of.&lt;/p&gt;&#xA;&#xA;&lt;h3&gt;Your favorite software, SK&lt;/h3&gt;&#xA;&#xA;&lt;p&gt;It may be morbid, but it has to be said: many, many programs become a &amp;ldquo;silent key&amp;rdquo; with their creators. No one had the source, so there was no way to continue fixing bugs and adding features after the death of the original author. This is also a liability for emergency communications and I&amp;rsquo;m shocked that (for example) Vara has made inroads there. As far as I can tell, it&amp;rsquo;s all dependent on one guy.&lt;/p&gt;&#xA;&#xA;&lt;p&gt;Even if the author has the forethought to make a plan for releasing the code after they burn out or shuffle off this mortal coil, no one else is experienced in developing it and there&amp;rsquo;s no incentive to make it easy for others to build. Likewise, there&amp;rsquo;s no reason for the author to produce developer-focused documentation. Having the code itself is only a small part of the equation.&lt;/p&gt;&#xA;&#xA;&lt;p&gt;You might think this issue is mitigated if the software is offered by a company. Not so. Companies are bought out or fail all the time. A great many companies offered packet radio software in the 90s and 2000s. Almost none of them are around today.&lt;/p&gt;&#xA;&#xA;&lt;h2&gt;An aside about hardware&lt;/h2&gt;&#xA;&#xA;&lt;p&gt;Although the move to software modems has been a good thing overall for amateur radio, old-style hardware modems (&amp;ldquo;TNCs&amp;rdquo;) did have one advantage: cross platform support was &amp;ldquo;free&amp;rdquo; since they tended to operate over serial connections that worked on all operating systems. That was just about their only advantage though, as they were bulky and expensive. They were also full of proprietary firmware. As an example, although SCS Pactor modems are every bit as proprietary as Vara, they are simple to use with any operating system and architecture.&lt;/p&gt;&#xA;&#xA;&lt;h2&gt;Success Stories&lt;/h2&gt;&#xA;&#xA;&lt;p&gt;There is hope however. Some of the most popular packages in amateur radio today are open source:&lt;/p&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;WSJT-X&lt;/li&gt;&#xA;&lt;li&gt;Fldigi&lt;/li&gt;&#xA;&lt;li&gt;Pat&lt;/li&gt;&#xA;&lt;li&gt;ARDOP&lt;/li&gt;&#xA;&lt;li&gt;Direwolf&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&#xA;&lt;p&gt;You can take a look at the source of any of these programs and learn something. If something doesn&amp;rsquo;t work the way you want it, you change change it! If you don&amp;rsquo;t know how to develop software, it&amp;rsquo;s quite likely someone else has had the same idea already. They can redistribute their own version or contribute back to the original.&lt;/p&gt;&#xA;&#xA;&lt;p&gt;The &lt;a href=&#34;https://www.ardc.net/&#34;&gt;ARDC&lt;/a&gt; is doing great work in this area, funding important pursuits like &lt;a href=&#34;https://github.com/Rhizomatica/mercury&#34;&gt;Mercury&lt;/a&gt;, a high-performance software modem.&lt;/p&gt;&#xA;&#xA;&lt;h2&gt;Closing Thoughts&lt;/h2&gt;&#xA;&#xA;&lt;p&gt;There&amp;rsquo;s nothing illegal about using proprietary software on the air in the USA, provided protocols are &amp;ldquo;documented&amp;rdquo;. It should be noted that what qualifies as &amp;ldquo;documented&amp;rdquo; has never been precisely defined by either the FCC or case law.&lt;/p&gt;&#xA;&#xA;&lt;p&gt;This blog post is not an invitation to zealotry: sometimes practicality must trump idealism, and this is why I own a (secondhand) Pactor modem and am a paid Vara subscriber. I make it a point, however, to use Open Source Software whenever possible.&lt;/p&gt;&#xA;&#xA;&lt;p&gt;This &lt;em&gt;is&lt;/em&gt; an invitation to support Open Source and Free Software by using it over alternatives, contributing when you can, and choosing open source when you write new software.&lt;/p&gt;&#xA;</description>
      <guid>https://jbo.io/oss-ham-radio.html</guid>
      <pubDate>Tue, 17 Sep 2024 21:21:41 -0400</pubDate>
    </item>
    <item>
      <title>RSS Redux</title>
      <link>https://jbo.io/rss-redux.html</link>
      <description>&lt;h2&gt;RSS Redux&lt;/h2&gt;&#xA;&#xA;&lt;p&gt;It&amp;rsquo;s been a while since my last post - lots going on in life. I&amp;rsquo;m back and looking to rehash a previous topic: RSS via email. In a &lt;a href=&#34;/rss2email.html&#34;&gt;previous post&lt;/a&gt;, I talked about rss2email on SDF. I eventually moved that setup over to another pubnix called radiofreqs.space, but that was taken offline at some point in 2021. This left me with no good way to get RSS feed updates delivered to my inbox. It was a good time to look into an alternative anyway as I was already moving my personal email over to Migadu, but that&amp;rsquo;s another post!&lt;/p&gt;&#xA;&#xA;&lt;h3&gt;FeedMail&lt;/h3&gt;&#xA;&#xA;&lt;p&gt;My next move was to scour HackerNews threads for solutions as I can&amp;rsquo;t have been the only person too lazy to set up a personal server for sending email from one of my domains. I located a comment mentioning &lt;a href=&#34;https://feedmail.org&#34;&gt;FeedMail&lt;/a&gt;, which remains my primary solution to this day. Just like rss2email, it will send you a separate email for each new post from every feed you&amp;rsquo;re subscribed to.&lt;/p&gt;&#xA;&#xA;&lt;p&gt;The setup is very straightforward and the site itself has a pleasing, minimal look. It supports having separate emails for administration and feeds, even supporting different emails on a per-feed basis. The &amp;ldquo;credit&amp;rdquo; system is very easy to understand and hopefully will make FeedMail sustainable in the long term. It supports OPML import, so I was quickly off to the races. I had a minor issue with one of my feeds not following the Atom standard properly and I found FeedMail&amp;rsquo;s maintainer to be incredibly responsive.&lt;/p&gt;&#xA;&#xA;&lt;p&gt;However, once I was forced to step back and re-evaluate my RSS feed consumption, I realized that there were several feeds I followed that don&amp;rsquo;t fit nicely into the one-email-per-post model. These feeds shared a couple characteristics:&lt;/p&gt;&#xA;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;They were high traffic, commonly 5-10 new posts during an average weekday&lt;/li&gt;&#xA;&lt;li&gt;I was interested in, at most, 1 or 2 of those new posts on a given day. The others were deleted after a glance at the title. My first thought was maybe I shouldn&amp;rsquo;t be following these feeds at all but I didn&amp;rsquo;t want to give up those interesting articles buried in the noise.&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&#xA;&lt;p&gt;Given the payment system that FeedMail uses, these feeds were burning a lot of credits sending me emails I would immediately delete. Back to HN to find a complementary solution!&lt;/p&gt;&#xA;&#xA;&lt;h3&gt;RSS by email&lt;/h3&gt;&#xA;&#xA;&lt;p&gt;Another helpful HN comment led me to &lt;a href=&#34;https://rssby.email&#34;&gt;RSS by email&lt;/a&gt;. This service is even more minimalist than FeedMail: you interact with it solely through the use of email. Special messages to the service are used to manage feeds, and you get a lightly-formatted digest of all new articles daily at a UTC hour of your choosing. This is the perfect solution for the issue of feeds with low signal to noise ratio. Now, I can just scan past any articles I don&amp;rsquo;t care about each day.&lt;/p&gt;&#xA;&#xA;&lt;p&gt;As far as I can tell, RSS by email is being run as a hobby by its creator, so I worry about its long-term viability. There is no donation link that I can find.&lt;/p&gt;&#xA;&#xA;&lt;p&gt;That being said, my heuristic going forward is that low-traffic or otherwise high priority feeds are managed by FeedMail, while firehose-type feeds are handled by RSS by email.&lt;/p&gt;&#xA;&#xA;&lt;h3&gt;Thanks!&lt;/h3&gt;&#xA;&#xA;&lt;p&gt;My goal for 2022 is to post more than one blog post every two years&amp;hellip;&lt;/p&gt;&#xA;</description>
      <guid>https://jbo.io/rss-redux.html</guid>
      <pubDate>Thu, 06 Jan 2022 11:07:41 -0500</pubDate>
    </item>
    <item>
      <title>Images from the ISS</title>
      <link>https://jbo.io/iss-sstv.html</link>
      <description>&lt;h2&gt;Images from the ISS&lt;/h2&gt;&#xA;&#xA;&lt;p&gt;Last year I became a licensed amateur radio operator, first getting my Technician license and ultimately getting my General class license a month or so later. While I was a Technician class operator with limited privileges on the HF bands, I sought out things to do with my full rights on VHF and UHF frequencies.&lt;/p&gt;&#xA;&#xA;&lt;p&gt;Turns out, there are quite a few satellites in Low Earth Orbit that are usable by ham radio operators to communicate with one another over long distances. They&amp;rsquo;re basically signal repeaters in the sky. Anyway, this post is not about those satellites, it&amp;rsquo;s about the International Space Station (ISS). The ISS has similar capabilities to the amateur satellites I mentioned - you can even talk to the crew sometimes! One other thing the ISS can do is send images receivable by anyone with a handheld radio or RTL-SDR.&lt;/p&gt;&#xA;&#xA;&lt;h3&gt;Slow-Scan Television&lt;/h3&gt;&#xA;&#xA;&lt;p&gt;The ISS crew sends these images on a VHF frequency (145.800 MHz) using a mode called Slow-Scan Television (SSTV). It&amp;rsquo;s essentially images over radio and &lt;a href=&#34;https://www.sigidwiki.com/wiki/SSTV&#34;&gt;it sounds like this&lt;/a&gt;. Typically the images are sent by the ISS crew to commemorate specific events and the broadcast times are &lt;a href=&#34;https://ariss-sstv.blogspot.com/&#34;&gt;announced in advance&lt;/a&gt;.&lt;/p&gt;&#xA;&#xA;&lt;h3&gt;Receiving the Signal&lt;/h3&gt;&#xA;&#xA;&lt;p&gt;As I mentioned, there are many ways of receiving and decoding the SSTV signal from the ISS, but you&amp;rsquo;ll need 4 basic things:&lt;/p&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;p&gt;A receiver that can pull in FM signals on 145.800 MHz. This could be a handheld radio that has coverage on that frequency or something general like an RTL-SDR&lt;/p&gt;&lt;/li&gt;&#xA;&#xA;&lt;li&gt;&lt;p&gt;An antenna: I&amp;rsquo;ve found that the signals sent by the ISS are pretty strong and can be received with even a marginal antenna like the dipole that comes with the RTL-SDR&lt;/p&gt;&lt;/li&gt;&#xA;&#xA;&lt;li&gt;&lt;p&gt;Pass-prediction software: The ISS is not geosynchronous like the satellites that stream television or radio. It moves through the sky. Plug &amp;ldquo;ISS pass prediction&amp;rdquo; into your favorite search engine or just use the ISS Detector app like I do.&lt;/p&gt;&lt;/li&gt;&#xA;&#xA;&lt;li&gt;&lt;p&gt;Decoding software: Something to turn the sounds into an image. Again, searching &amp;ldquo;SSTV decoding software&amp;rdquo; will turn up answers, but I use the Robot36 app on Android. The ISS typically uses the &amp;ldquo;PD120&amp;rdquo; format.&lt;/p&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&#xA;&lt;h3&gt;My Method&lt;/h3&gt;&#xA;&#xA;&lt;p&gt;I have an antenna specifically designed for working Low-Earth Orbit satellites called an Arrow Antenna, but I&amp;rsquo;ve been told that more basic antenna work fine. I connected that antenna to my handheld transceiver (Yaesu FT-60R) and opened the Robot36 app on my phone. I then made sure the phone was close to the radio and turned the volume up. Since I was using a highly directional antenna, I used the ISS Detector app to help me point it correctly at the sky. The Robot36 app began decoding once it heard the start of the image signal, ultimately producing this&lt;/p&gt;&#xA;&#xA;&lt;p&gt;&lt;img src=&#34;/20191231-iss-sstv-cropped.jpg&#34; alt=&#34;ISS SSTV image&#34; /&gt;&lt;/p&gt;&#xA;&#xA;&lt;p&gt;which I saved off from my phone and cropped it a bit. Not bad for an image from space!&lt;/p&gt;&#xA;</description>
      <guid>https://jbo.io/iss-sstv.html</guid>
      <pubDate>Wed, 15 Jan 2020 19:51:43 -0500</pubDate>
    </item>
    <item>
      <title>Reviving a Raspberry Pi with NetBSD</title>
      <link>https://jbo.io/rpi-netbsd.html</link>
      <description>&lt;h2&gt;Reviving a Raspberry Pi with NetBSD&lt;/h2&gt;&#xA;&#xA;&lt;p&gt;I started getting into BSDs (specifically NetBSD) as a result of my membership in &lt;a href=&#34;https://sdf.org&#34;&gt;SDF&lt;/a&gt;. I have a good deal of Linux experience but hadn&amp;rsquo;t really had a good reason to try out one of the BSD operating systems. I remembered I had a first-gen Raspberry Pi (Model B) languishing in my desk drawer and decided to put NetBSD&amp;rsquo;s slogan (&amp;ldquo;Of course it runs NetBSD!&amp;rdquo;) to the test.&lt;/p&gt;&#xA;&#xA;&lt;p&gt;The Model B features a 32-bit armv6 chip and 256MB of RAM (!). I poked around on the NetBSD homepage and found &lt;a href=&#34;http://wiki.netbsd.org/ports/evbarm/raspberry_pi/#index2h1&#34;&gt;this&lt;/a&gt; page which told me &amp;ldquo;earmv6hf&amp;rdquo; was the NetBSD port I wanted for an rpi 1.&lt;/p&gt;&#xA;&#xA;&lt;p&gt;&lt;a href=&#34;https://opensource.com/article/19/3/netbsd-raspberry-pi&#34;&gt;This article&lt;/a&gt; on opensource.com does a fantastic job of showing the initial steps to get it running, and I won&amp;rsquo;t attempt to reproduce those here. Instead, I want to fill in some gaps.&lt;/p&gt;&#xA;&#xA;&lt;h3&gt;WiFi&lt;/h3&gt;&#xA;&#xA;&lt;p&gt;So you&amp;rsquo;ve followed the opensource.com article above and you&amp;rsquo;re sitting at a root prompt with your Pi hooked up to a TV and a keyboard plugged into one of the two USB ports. Now, you want to be able to use the Pi as a &amp;ldquo;server&amp;rdquo; and stash it somewhere. Unfortunately, Pis this old don&amp;rsquo;t have onboard WiFi, so you&amp;rsquo;ll need an adapter. This is one area where NetBSD is a bit rough around the edges: the WiFi adapter support isn&amp;rsquo;t as strong as some Linux distributions. Even if your Pi has onboard WiFi, it is unlikely that NetBSD supports it.&lt;/p&gt;&#xA;&#xA;&lt;p&gt;I used &lt;a href=&#34;https://www.amazon.com/gp/product/B003MTTJOY/&#34;&gt;this&lt;/a&gt; adapter and I know that it works out of the box with NetBSD 8.0. If that Amazon link ever dies, the adapter is the Edimax EW-7811Un. Be aware that this adapter does not have dual-band support, so you&amp;rsquo;ll have to stick with 802.11n. This means you can&amp;rsquo;t use the 5GHz band of your router.&lt;/p&gt;&#xA;&#xA;&lt;p&gt;Once you have the adapter, shut down the pi at the root prompt with &lt;code&gt;shutdown -p now&lt;/code&gt;. You&amp;rsquo;ll eventually see a message saying &amp;ldquo;press any key to restart&amp;rdquo;. Once you see this, unplug the Pi. Now, plug in the Edimax adapter to the empty USB port and reconnect the Pi. When you&amp;rsquo;re back at your root prompt, follow the instructions &lt;a href=&#34;https://wiki.netbsd.org/tutorials/how_to_use_wpa_supplicant/&#34;&gt;here&lt;/a&gt; to set up wpa_supplicant, which is the NetBSD daemon that handles WPA/WPA2. I was able to copy/paste the &amp;ldquo;simple&amp;rdquo; example from the wpa_supplicant page:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code&gt;ctrl_interface=/var/run/wpa_supplicant&#xA;ctrl_interface_group=wheel&#xA;network={&#xA;        ssid=&amp;quot;my favourite network&amp;quot;&#xA;        psk=&amp;quot;hunter2&amp;quot;&#xA;}&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&#xA;&lt;p&gt;I replaced the ssid and psk with my network&amp;rsquo;s info. The tutorial tells you to edit /etc/rc.conf and add the following lines:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code&gt;wpa_supplicant=YES&#xA;wpa_supplicant_flags=&amp;quot;-i iwn0 -c /etc/wpa_supplicant.conf&amp;quot;&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&#xA;&lt;p&gt;&lt;code&gt;iwn0&lt;/code&gt; is a network interface - find yours by typing &lt;code&gt;ifconfig&lt;/code&gt; and looking for interface name whose address starts with &lt;code&gt;74:da:38&lt;/code&gt; (provided you&amp;rsquo;re using my recommended adapter). Mine is &lt;code&gt;urtwn0&lt;/code&gt;, so I replaced &lt;code&gt;iwn0&lt;/code&gt; in the above command with &lt;code&gt;urtwn0&lt;/code&gt;. Now you should be able to find the Pi on your network and SSH into it - no need for the TV anymore!&lt;/p&gt;&#xA;&#xA;&lt;h3&gt;bash issues&lt;/h3&gt;&#xA;&#xA;&lt;p&gt;After enabling bash as my shell per the opensource.com guide, I found that some things were not on my $PATH by default. I loaded a custom .bashrc file to get my normal aliases, and found that &amp;ldquo;pkg_add&amp;rdquo; was no longer on the PATH. Also, it seems that no .bash_profile is created by default in your user&amp;rsquo;s home directory. To fix both these issues, add the below lines to the newly-created ~/.bash_profile:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code&gt;PATH=$HOME/bin:/bin:/sbin:/usr/bin:/usr/sbin:/usr/X11R7/bin:/usr/pkg/bin&#xA;PATH=${PATH}:/usr/pkg/sbin:/usr/games:/usr/local/bin:/usr/local/sbin&#xA;&#xA;source .bashrc&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&#xA;&lt;h3&gt;pkgin&lt;/h3&gt;&#xA;&#xA;&lt;p&gt;The opensource.com guide talks about pkgsrc, which allows for the installation of both binary and source packages for NetBSD. If you&amp;rsquo;re coming from Linux though, you&amp;rsquo;re probably expecting something that works like yum or apt. pkgsrc does not work this way and crucially, it doesn&amp;rsquo;t have a great story for updates. &lt;a href=&#34;http://pkgin.net/&#34;&gt;Pkgin&lt;/a&gt; is what you&amp;rsquo;re looking for here. Run &lt;code&gt;pkg_add -v pkgin&lt;/code&gt; as root and then follow the guides on the pkgin homepage from there. Pkgin uses pkgsrc under the hood. Remember that your old Pi 1 is very underpowered, so expect package installations to take a while.&lt;/p&gt;&#xA;&#xA;&lt;p&gt;After you&amp;rsquo;ve installed pkgin, there will be a message telling you to run &lt;code&gt;pkgin update&lt;/code&gt;. Before you do this, check the file in /usr/pkg/etc/pkgin/repositories.conf. The url in there was wrong by default on my installation. For an rpi 1, it should be &lt;code&gt;ftp://ftp.netbsd.org/pub/pkgsrc/packages/NetBSD/earmv6hf/8.0/All&lt;/code&gt;. Once that&amp;rsquo;s correct, run &lt;code&gt;pkgin update&lt;/code&gt;.&lt;/p&gt;&#xA;&#xA;&lt;h3&gt;Assorted gotchas&lt;/h3&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;p&gt;&lt;em&gt;Never&lt;/em&gt; unplug the Pi without running &lt;code&gt;shutdown -p now&lt;/code&gt; first and waiting a bit if you&amp;rsquo;re doing that over SSH. The older models seem especially sensitive to corruption of the SD card. If this happens to you, you&amp;rsquo;ll have to hook your Pi up to a TV again and single-user mode will be active. From there you can use &lt;code&gt;fsck -tufs -y /dev/&amp;lt;your sd card&amp;gt;&lt;/code&gt; You can typically find your sdcard name by running &lt;code&gt;fdisk&lt;/code&gt;. In many scenarios this will repair your disk. If it fails for some reason, well, GOTO the top of this guide and reimage.&lt;/p&gt;&lt;/li&gt;&#xA;&#xA;&lt;li&gt;&lt;p&gt;Many of the tools you&amp;rsquo;re used to having out of the box in Linux distributions will not be installed. NetBSD is highly configurable and does not attempt to decide what you should use. Most things will be easily available on pkgin though.&lt;/p&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&#xA;&lt;h3&gt;Go forth!&lt;/h3&gt;&#xA;&#xA;&lt;p&gt;Now you can install the packages you&amp;rsquo;re used to, like vim, git, etc. Happy hacking!&lt;/p&gt;&#xA;&#xA;&lt;h3&gt;Thanks!&lt;/h3&gt;&#xA;&#xA;&lt;p&gt;I hope you&amp;rsquo;ve enjoyed this post. Hit me up on &lt;a href=&#34;https://mastodon.sdf.org/@jboverf&#34;&gt;Mastodon&lt;/a&gt; with any questions or comments!&lt;/p&gt;&#xA;</description>
      <guid>https://jbo.io/rpi-netbsd.html</guid>
      <pubDate>Tue, 23 Apr 2019 10:35:48 -0400</pubDate>
    </item>
    <item>
      <title>The Best RSS Reader Is Your Inbox</title>
      <link>https://jbo.io/rss2email.html</link>
      <description>&lt;h2&gt;The Best RSS Reader Is Your Inbox&lt;/h2&gt;&#xA;&#xA;&lt;p&gt;I was very late to the RSS game, especially for a programmer. It wasn&amp;rsquo;t until a little over a year ago that I got serious about making RSS/Atom a part of my daily content diet. Since that time, I&amp;rsquo;ve used a few readers, namely &lt;a href=&#34;https://www.inoreader.com&#34;&gt;Inoreader&lt;/a&gt; and an instance of &lt;a href=&#34;https://tt-rss.org/&#34;&gt;Tiny Tiny RSS&lt;/a&gt; lovingly hosted by &lt;a href=&#34;https://sdf.org&#34;&gt;SDF&lt;/a&gt; for the benefit of MetaARPA members like myself. I enjoyed both those products, but it always felt a bit like my feeds were &amp;ldquo;locked away&amp;rdquo; (even though OPML export is easy in both tools).&lt;/p&gt;&#xA;&#xA;&lt;p&gt;It finally dawned on me over the last week or so that all RSS readers (that I&amp;rsquo;ve used anyway) really just reinvent email. There&amp;rsquo;s folders the navigate things and &amp;ldquo;stars&amp;rdquo; to save articles you want to refer to later. There&amp;rsquo;s the concept of read and unread articles. You can load images or just view text. Sometimes the full content is available, other times there&amp;rsquo;s just a link. Most importantly, I find that RSS readers implement &amp;ldquo;rules&amp;rdquo; that do not compare to what is available from various email clients. I thought to myself: &amp;ldquo;I bet I could write a tool that scraped RSS feeds and sent emails very easily!&amp;rdquo;. Turns out, I&amp;rsquo;m far from the first to have this idea (much like many of my ideas).&lt;/p&gt;&#xA;&#xA;&lt;h3&gt;rss2email&lt;/h3&gt;&#xA;&#xA;&lt;p&gt;Enter &lt;a href=&#34;https://github.com/rss2email/rss2email&#34;&gt;rss2email&lt;/a&gt;. It appears this tool has been around for quite a while and I&amp;rsquo;m very late to the game (again). I imported my aforementioned OPML file with &lt;code&gt;r2e opmlimport&lt;/code&gt; and I was off to the races. Well&amp;hellip;sort of. You have to configure a way to send email of course! Since I was running this on the SDF Cluster, I was able to use their SMTP server by adjusting the config file (&lt;code&gt;~/.config/rss2email.cfg&lt;/code&gt;). By default, it wants to use sendmail and to be honest I was more comfortable with straight SMTP. The key setting here is &lt;code&gt;email-protocol&lt;/code&gt;. There are other SMTP settings in there that were pretty clear in my opinion.&lt;/p&gt;&#xA;&#xA;&lt;p&gt;Time to read some feeds! Be sure to invoke &lt;code&gt;r2e run&lt;/code&gt; the first time with the &lt;code&gt;--no-send&lt;/code&gt; flag or be prepared for a deluge of email as it sends you everything from all your feeds! After that, &lt;code&gt;r2e run&lt;/code&gt; will suffice. Helpfully, r2e has &lt;code&gt;-V&lt;/code&gt; flags that control the verbosity of its output. Once you have a manual run that worked, I&amp;rsquo;d recommend setting up a cron job to run it on a schedule.&lt;/p&gt;&#xA;&#xA;&lt;p&gt;Once I got all that working, I perused the other options in the config file, including some for HTML mail. I don&amp;rsquo;t typically like HTML mail, but the formatting used by default when you enable HTML is very light. There is a &amp;ldquo;digest&amp;rdquo; mode that sends one email for each feed for a given run, but it attaches the articles as emails to the digest email, which was off-putting to me. Maybe you like that. In any case, hopefully this helps someone else even later to the game than me!&lt;/p&gt;&#xA;&#xA;&lt;h3&gt;Other Options&lt;/h3&gt;&#xA;&#xA;&lt;p&gt;A quick web search indicates that there are services out there which will do this for you, though many seem to be targeted at businesses wanting to email their customers when they have a new post. One of those products might work better if you aren&amp;rsquo;t able to set up rss2email for whatever reason!&lt;/p&gt;&#xA;&#xA;&lt;h3&gt;Thanks!&lt;/h3&gt;&#xA;&#xA;&lt;p&gt;I hope you&amp;rsquo;ve enjoyed this post. Hit me up on &lt;a href=&#34;https://mastodon.sdf.org/@jboverf&#34;&gt;Mastodon&lt;/a&gt; with any questions or comments!&lt;/p&gt;&#xA;</description>
      <guid>https://jbo.io/rss2email.html</guid>
      <pubDate>Wed, 03 Apr 2019 20:28:42 -0400</pubDate>
    </item>
    <item>
      <title>Server-Sent Events</title>
      <link>https://jbo.io/server-sent-events.html</link>
      <description>&lt;h2&gt;Server-Sent Events&lt;/h2&gt;&#xA;&#xA;&lt;p&gt;Often when the topic of web application notifications comes up, us web developers are quick to reach for WebSockets. There exists a wealth of information on them in the blogosphere and most importantly: on StackOverflow. However, WebSockets are too big a hammer for many of the cases in which they are most commonly used. Instead, I&amp;rsquo;d like you introduce you to &lt;a href=&#34;https://html.spec.whatwg.org/multipage/server-sent-events.html#server-sent-events&#34;&gt;Server Sent Events&lt;/a&gt;.&lt;/p&gt;&#xA;&#xA;&lt;h3&gt;Why use SSE?&lt;/h3&gt;&#xA;&#xA;&lt;p&gt;Unlike WebSockets, Server-Sent Events are for one-way communication (server pushes to client). When you&amp;rsquo;re implementing live notifications, this is all you typically need. If you really need full-duplex communication between client and server (maybe you&amp;rsquo;re making a chat app), well, then stick to WebSockets.&lt;/p&gt;&#xA;&#xA;&lt;p&gt;I find the main benefit of SSE to be its simplicity - it&amp;rsquo;s just HTTP(S)! When using WebSockets, the connection starts as HTTP, but an &amp;ldquo;Upgrade&amp;rdquo; request is sent and the connection is no longer plain HTTP. With SSE, you can continue to write HTTP handlers the way you always have in the language and framework of your choice. You just set some headers, and then write data to the client as you normally would (well, in a simple text format I&amp;rsquo;ll detail below&amp;hellip;). This means you don&amp;rsquo;t have to change anything about your web proxy or any other part of your stack. Feel free to continue to use the same authentication you use for all your other HTTP handlers. Sysadmins love it!&lt;/p&gt;&#xA;&#xA;&lt;p&gt;Furthermore, the JavaScript interface for working with Server-Sent Events handles automatic reconnects! The reconnect time is even configurable. With all that said, let&amp;rsquo;s move onto the SSE protocol.&lt;/p&gt;&#xA;&#xA;&lt;h3&gt;The SSE Protocol&lt;/h3&gt;&#xA;&#xA;&lt;p&gt;The SSE protocol is text-based, and has mercifully few concepts to learn. If all you want to do is send a line of text to the client, this is the format:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code&gt;data: something happened!\n\n&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&#xA;&lt;p&gt;The second newline means &amp;ldquo;send the message&amp;rdquo;. The first newline is there in case you want to send messages that span more than one line. This is useful for sending JSON:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code&gt;data: {\n&#xA;data: &amp;quot;foo&amp;quot;: &amp;quot;bar&amp;quot;\n&#xA;data: }\n\n&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&#xA;&lt;p&gt;Now, it may be the case that you want to have different kinds of messages in the same &amp;ldquo;pipe&amp;rdquo;. Maybe a table is being updated by others and rows can be added or removed. You want to notify the user of both kinds of changes. Well, SSE has you covered there too with the &amp;ldquo;event&amp;rdquo; tag:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code&gt;event: add\n&#xA;data: added one\n\n&#xA;&#xA;event: delete\n&#xA;data: deleted one\n\n&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&#xA;&lt;p&gt;Finally, you can give events ids with the &amp;ldquo;id&amp;rdquo; tag:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code&gt;id: 1\n&#xA;data: i&#39;m one!\n\n&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&#xA;&lt;p&gt;It&amp;rsquo;s past time to give the people what they want: code samples! All server-side code in this post will be in &lt;a href=&#34;https://golang.org&#34;&gt;Go&lt;/a&gt;, but it should translate to the language of your choice easily.&lt;/p&gt;&#xA;&#xA;&lt;h3&gt;The Backend&lt;/h3&gt;&#xA;&#xA;&lt;p&gt;The implementation of a full SSE server is out of the scope of this blog post (which is already too long), if you&amp;rsquo;d like something more in-depth for Go, I heartily recommend &lt;a href=&#34;https://thoughtbot.com/blog/writing-a-server-sent-events-server-in-go&#34;&gt;this&lt;/a&gt; post from ThoughtBot.&lt;/p&gt;&#xA;&#xA;&lt;p&gt;Instead, we&amp;rsquo;ll cover the basics of writing an HTTP handler to do Server-Sent Events in Go. As is customary for lazy bloggers like myself, I&amp;rsquo;ve elided error handling. Furthermore, the code assumes you have a some sort of broker which hands out channels (little typesafe queues that Go provides) for clients to read from. This broker also allows ID generation and registering/deregistering clients. Don&amp;rsquo;t get bogged down here, just think &amp;ldquo;this broker returns things that we can read messages from&amp;rdquo;. More information on doing this is in the ThoughtBot blog post referenced earlier. I haven&amp;rsquo;t included that here because it will be heavily dependent on your use case.&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code&gt;package main&#xA;&#xA;func main() {&#xA;&#x9;// set up the fictional broker&#xA;&#x9;broker := &amp;amp;Broker{}&#xA;&#x9;http.HandleFunc(eventHandler(broker))&#xA;&#xA;&#x9;http.ListenAndServe(&amp;quot;:3131&amp;quot;, nil)&#xA;}&#xA;&#xA;func eventHandler(broker *Broker) http.HandlerFunc {&#xA;&#x9;return func(w http.ResponseWriter, r *http.Request) {&#xA;&#x9;&#x9;id := broker.GenerateID()&#xA;&#x9;&#x9;msgChan := broker.RegisterNewClient(id)&#xA;&#xA;&#x9;&#x9;w.Header().Set(&amp;quot;Content-Type&amp;quot;, &amp;quot;text/event-stream&amp;quot;)&#xA;&#x9;&#x9;w.Header().Set(&amp;quot;Cache-Control&amp;quot;, &amp;quot;no-cache&amp;quot;)&#xA;&#x9;&#x9;w.Header().Set(&amp;quot;Connection&amp;quot;, &amp;quot;keep-alive&amp;quot;)&#xA;&#xA;&#x9;&#x9;// in real code, check to make sure this cast works!&#xA;&#x9;&#x9;flusher, _ := w.(http.Flusher)&#xA;&#xA;&#x9;&#x9;for {&#xA;&#x9;&#x9;&#x9;select {&#xA;&#x9;&#x9;&#x9;case msg := &amp;lt;-msgChan:&#xA;&#x9;&#x9;&#x9;&#x9;fmt.Fprintf(w, &amp;quot;data: %s\n\n&amp;quot;, msg)&#xA;&#x9;&#x9;&#x9;&#x9;flusher.Flush()&#xA;&#x9;&#x9;&#x9;case &amp;lt;-r.Context().Done():&#xA;&#x9;&#x9;&#x9;&#x9;broker.DeregisterClient(id)&#xA;&#x9;&#x9;&#x9;&#x9;return&#xA;&#x9;&#x9;&#x9;}&#xA;&#x9;&#x9;}&#xA;&#x9;}&#xA;}&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&#xA;&lt;p&gt;Ok, there are some things to unpack here. I don&amp;rsquo;t want to talk too much about the Go-specific parts, because this is an SSE tutorial. Here are the important bits:&lt;/p&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;p&gt;The setting of the headers, specifically the Content-Type header. This &lt;strong&gt;must&lt;/strong&gt; be set to &lt;code&gt;text/event-stream&lt;/code&gt;. The other headers are for ensuring that the browser doesn&amp;rsquo;t cache messages and telling it that this connection will be long-lived.&lt;/p&gt;&lt;/li&gt;&#xA;&#xA;&lt;li&gt;&lt;p&gt;Writing data out to the client with &lt;code&gt;Fprintf&lt;/code&gt;. We&amp;rsquo;re using the aforementioned format here with the &lt;code&gt;data:&lt;/code&gt; tag.&lt;/p&gt;&lt;/li&gt;&#xA;&#xA;&lt;li&gt;&lt;p&gt;The &amp;ldquo;infinite&amp;rdquo; for loop, which writes data to the client until the user navigates away from the page.&lt;/p&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&#xA;&lt;p&gt;I claim that this is conceptually straightfoward when compared to WebSockets and allows nearly endless tailoring to fit the needs of your application. To summarize: set the right headers, and write data out in the SSE format until the user leaves or otherwise says they don&amp;rsquo;t want updates anymore.&lt;/p&gt;&#xA;&#xA;&lt;h3&gt;The Frontend&lt;/h3&gt;&#xA;&#xA;&lt;p&gt;The &lt;a href=&#34;https://developer.mozilla.org/en-US/docs/Web/API/EventSource&#34;&gt;EventSource&lt;/a&gt; interface is all you need!&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-javascript&#34;&gt;var client = new EventSource(&amp;quot;https://mysite.local:3131&amp;quot;)&#xA;client.onmessage = (e) =&amp;gt; { console.log(e.data) }&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&#xA;&lt;p&gt;&lt;code&gt;onerror&lt;/code&gt; and &lt;code&gt;onopen&lt;/code&gt; handlers are also available for handling errors and performing tasks when the connection is opened.&lt;/p&gt;&#xA;&#xA;&lt;p&gt;You might do any number of things in the &lt;code&gt;onmessage&lt;/code&gt; handler, perhaps rendering a new row in a table, or adding the line of data you just received to an array and having Vue/React/etc rerender your component!&lt;/p&gt;&#xA;&#xA;&lt;p&gt;If you broke up your events by type like I mentioned earlier, you can set up your handlers like this:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-javascript&#34;&gt;client.addEventListener(&#39;add&#39;, (e) =&amp;gt; { console.log(e.data) }, false)&#xA;client.addEventListener(&#39;delete&#39;, (e) =&amp;gt; { console.log(e.data) }, false)&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&#xA;&lt;h3&gt;Thanks!&lt;/h3&gt;&#xA;&#xA;&lt;p&gt;I hope you&amp;rsquo;ve enjoyed this post. Hit me up on &lt;a href=&#34;https://mastodon.sdf.org/@jboverf&#34;&gt;Mastodon&lt;/a&gt;, especially if I did something wrong!&lt;/p&gt;&#xA;</description>
      <guid>https://jbo.io/server-sent-events.html</guid>
      <pubDate>Sun, 17 Mar 2019 17:47:41 -0400</pubDate>
    </item>
  </channel>
</rss>