<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
  
  <title>Camiel Schoonens</title>
  <subtitle>Product and Technology Enthusiast</subtitle>
  <link href="https://camiel.schoonens.nl/feed.xml" rel="self" />
  <link href="https://camiel.schoonens.nl/" />
  <updated>2026-06-09T00:00:00Z</updated>
  <id>https://camiel.schoonens.nl/</id>
  <author>
    <name>Camiel Schoonens</name>
  </author>
  <entry>
    <title>Testing my Canon RF 15-35mm with a Benro Pro Filter Kit</title>
    <link href="https://camiel.schoonens.nl/blog/Benro-Test/" />
    <updated>2026-06-09T00:00:00Z</updated>
    <id>https://camiel.schoonens.nl/blog/Benro-Test/</id>
    <content type="html">&lt;p&gt;I recently added two new pieces to my &lt;a href=&quot;https://camiel.schoonens.nl/uses/&quot;&gt;photography gear&lt;/a&gt;, the Canon RF 15-35mm f/2.8L IS USM and the Benro Pro Filter Kit. The RF 15-35mm is a huge step up from the RF 16mm prime I have had in my bag for the last two years. I took both the new lens and the filter kit out for the first time tonight with the purpose to practice working with the setup before going on summer holiday.&lt;/p&gt;
&lt;p&gt;As you can see, the lens at the wide end at 15mm gives a sense of depth and space that works well in landscape photography, I look forward to using it a lot this summer in Costa Rica.&lt;/p&gt;
&lt;h2&gt;The filter kit&lt;/h2&gt;
&lt;p&gt;The Benro kit I bought includes a filter holder, a 2-stop graduated ND and a 3-stop graduated ND, plus a 6-stop solid ND. The system uses a slot-in holder with a gear-driven knob to position the graduated filters. The system is expensive but the build quality is really good. That said, I still need a lot of practice to get the filter positioning right consistently. One practical issue: the two graduated filters (2-stop and 3-stop) are nearly impossible to tell apart in the field. They are the same size and shape, and the difference in glass density is not visible to my eye. I need to find a way to tag or mark them so I can grab the right one without holding each up to the light.&lt;/p&gt;
&lt;h2&gt;What I shot&lt;/h2&gt;
&lt;p&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://camiel.schoonens.nl/img/gbJv-q7VNP-400.webp 400w, https://camiel.schoonens.nl/img/gbJv-q7VNP-800.webp 800w, https://camiel.schoonens.nl/img/gbJv-q7VNP-1600.webp 1600w&quot; sizes=&quot;100vw&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://camiel.schoonens.nl/img/gbJv-q7VNP-400.jpeg&quot; alt=&quot;Shot without filters&quot; width=&quot;1600&quot; height=&quot;1067&quot; srcset=&quot;https://camiel.schoonens.nl/img/gbJv-q7VNP-400.jpeg 400w, https://camiel.schoonens.nl/img/gbJv-q7VNP-800.jpeg 800w, https://camiel.schoonens.nl/img/gbJv-q7VNP-1600.jpeg 1600w&quot; sizes=&quot;100vw&quot;&gt;&lt;/picture&gt;
&lt;em&gt;Shot 1/3 - Canon EOS R6 Mark II + RF 15-35mm f/2.8 L IS @ 16mm without filters&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://camiel.schoonens.nl/img/6eHQPCIlwk-400.webp 400w, https://camiel.schoonens.nl/img/6eHQPCIlwk-800.webp 800w, https://camiel.schoonens.nl/img/6eHQPCIlwk-1600.webp 1600w&quot; sizes=&quot;100vw&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://camiel.schoonens.nl/img/6eHQPCIlwk-400.jpeg&quot; alt=&quot;3-stop graduated ND&quot; width=&quot;1600&quot; height=&quot;1067&quot; srcset=&quot;https://camiel.schoonens.nl/img/6eHQPCIlwk-400.jpeg 400w, https://camiel.schoonens.nl/img/6eHQPCIlwk-800.jpeg 800w, https://camiel.schoonens.nl/img/6eHQPCIlwk-1600.jpeg 1600w&quot; sizes=&quot;100vw&quot;&gt;&lt;/picture&gt;
&lt;em&gt;Shot 2/3 - Canon EOS R6 Mark II + RF 15-35mm f/2.8 L IS @ 16mm with a 3-stop graduated ND&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://camiel.schoonens.nl/img/QAYqK-8uwG-400.webp 400w, https://camiel.schoonens.nl/img/QAYqK-8uwG-800.webp 800w, https://camiel.schoonens.nl/img/QAYqK-8uwG-1600.webp 1600w&quot; sizes=&quot;100vw&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://camiel.schoonens.nl/img/QAYqK-8uwG-400.jpeg&quot; alt=&quot;3-stop graduated ND + 6-stop ND&quot; width=&quot;1600&quot; height=&quot;1067&quot; srcset=&quot;https://camiel.schoonens.nl/img/QAYqK-8uwG-400.jpeg 400w, https://camiel.schoonens.nl/img/QAYqK-8uwG-800.jpeg 800w, https://camiel.schoonens.nl/img/QAYqK-8uwG-1600.jpeg 1600w&quot; sizes=&quot;100vw&quot;&gt;&lt;/picture&gt;
&lt;em&gt;Shot 3/3 - Canon EOS R6 Mark II + RF 15-35mm f/2.8 L IS @ 16mm with a 3-stop graduated ND combined with 6-stop ND&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://camiel.schoonens.nl/img/Fd-FpeNSD5-400.webp 400w, https://camiel.schoonens.nl/img/Fd-FpeNSD5-800.webp 800w, https://camiel.schoonens.nl/img/Fd-FpeNSD5-1600.webp 1600w&quot; sizes=&quot;100vw&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://camiel.schoonens.nl/img/Fd-FpeNSD5-400.jpeg&quot; alt=&quot;2-stop graduated ND&quot; width=&quot;1600&quot; height=&quot;1067&quot; srcset=&quot;https://camiel.schoonens.nl/img/Fd-FpeNSD5-400.jpeg 400w, https://camiel.schoonens.nl/img/Fd-FpeNSD5-800.jpeg 800w, https://camiel.schoonens.nl/img/Fd-FpeNSD5-1600.jpeg 1600w&quot; sizes=&quot;100vw&quot;&gt;&lt;/picture&gt;
&lt;em&gt;Canon EOS R6 Mark II + RF 15-35mm f/2.8 L IS @ 15mm with a 2-stop graduated ND&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://camiel.schoonens.nl/img/1yJv2gp846-400.webp 400w, https://camiel.schoonens.nl/img/1yJv2gp846-800.webp 800w, https://camiel.schoonens.nl/img/1yJv2gp846-1600.webp 1600w&quot; sizes=&quot;100vw&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://camiel.schoonens.nl/img/1yJv2gp846-400.jpeg&quot; alt=&quot;3-stop graduated ND at 35mm&quot; width=&quot;1600&quot; height=&quot;1067&quot; srcset=&quot;https://camiel.schoonens.nl/img/1yJv2gp846-400.jpeg 400w, https://camiel.schoonens.nl/img/1yJv2gp846-800.jpeg 800w, https://camiel.schoonens.nl/img/1yJv2gp846-1600.jpeg 1600w&quot; sizes=&quot;100vw&quot;&gt;&lt;/picture&gt;
&lt;em&gt;Canon EOS R6 Mark II + RF 15-35mm f/2.8 L IS @ 35mm with a 3-stop graduated ND&lt;/em&gt;&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Tracking Max Verstappen&#39;s private jet with Home Assistant and ADS-B</title>
    <link href="https://camiel.schoonens.nl/lab/Where-Is-Max-Bot/" />
    <updated>2026-05-19T00:00:00Z</updated>
    <id>https://camiel.schoonens.nl/lab/Where-Is-Max-Bot/</id>
    <content type="html">&lt;p&gt;Every commercial and private aircraft broadcasting ADS-B is publicly visible to anyone willing to poll the right API. Max Verstappen&#39;s Dassault Falcon 900 with registration PH-UTL (Unleash The Lion) is no exception. I thought it would be fun to wire this into Home Assistant and have a dedicated Mastodon bot post updates whenever the jet takes off or lands somewhere.&lt;/p&gt;
&lt;p&gt;The result is &lt;a href=&quot;https://social.schoonens.nl/@waarismax&quot;&gt;@waarismax&lt;/a&gt; on my own Fediverse instance. It posts publicly whenever PH-UTL moves. The bot posts in Dutch.&lt;/p&gt;
&lt;p&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://camiel.schoonens.nl/img/xtC8MIrFTK-400.webp 400w, https://camiel.schoonens.nl/img/xtC8MIrFTK-800.webp 800w, https://camiel.schoonens.nl/img/xtC8MIrFTK-1238.webp 1238w&quot; sizes=&quot;100vw&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://camiel.schoonens.nl/img/xtC8MIrFTK-400.jpeg&quot; alt=&quot;WhereIsMax bot post&quot; width=&quot;1238&quot; height=&quot;1146&quot; srcset=&quot;https://camiel.schoonens.nl/img/xtC8MIrFTK-400.jpeg 400w, https://camiel.schoonens.nl/img/xtC8MIrFTK-800.jpeg 800w, https://camiel.schoonens.nl/img/xtC8MIrFTK-1238.jpeg 1238w&quot; sizes=&quot;100vw&quot;&gt;&lt;/picture&gt;&lt;/p&gt;
&lt;h2&gt;Why&lt;/h2&gt;
&lt;p&gt;Because I could.&lt;/p&gt;
&lt;h2&gt;Finding the data&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://api.adsb.lol&quot;&gt;adsb.lol&lt;/a&gt; is a free, no-auth API that queries live ADS-B data by ICAO hex code. A single GET request returns the current position and state of any broadcasting aircraft:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;https://api.adsb.lol/v2/icao/4867e6
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When the aircraft is airborne, the response includes an &lt;code&gt;ac&lt;/code&gt; array with fields for altitude, ground speed, lat/lon, callsign, and more. When it&#39;s on the ground with its transponder off, &lt;code&gt;ac&lt;/code&gt; is empty and &lt;code&gt;total&lt;/code&gt; is &lt;code&gt;0&lt;/code&gt;. That on/off toggle is the trigger for the automations.&lt;/p&gt;
&lt;h2&gt;The sensor setup&lt;/h2&gt;
&lt;p&gt;Everything lives in &lt;code&gt;configuration.yaml&lt;/code&gt;. A &lt;code&gt;rest:&lt;/code&gt; block polls the API every 60 seconds and derives six sensors from the response: status (airborne/offline), altitude, speed, latitude, longitude, and flight number.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;max_jet_status&lt;/code&gt; sensor is the key one. It flips between &lt;code&gt;airborne&lt;/code&gt; and &lt;code&gt;offline&lt;/code&gt; and is what the takeoff and landing automations watch.&lt;/p&gt;
&lt;h2&gt;Reverse geocoding the position&lt;/h2&gt;
&lt;p&gt;Lat/lon coordinates are not readable in a Mastodon post. I wanted &amp;quot;Keulen, Duitsland&amp;quot; rather than &amp;quot;50.868, 7.121&amp;quot;. OpenStreetMap&#39;s &lt;a href=&quot;https://nominatim.openstreetmap.org&quot;&gt;Nominatim&lt;/a&gt; API handles this for free, with no API key required, as long as you respect the fair use policy.&lt;/p&gt;
&lt;p&gt;Rather than polling Nominatim on a fixed interval, a &lt;code&gt;rest_command&lt;/code&gt; calls it on demand and a dedicated automation fires that command whenever the aircraft&#39;s latitude changes state. This means Nominatim only gets called when the aircraft is actually moving.&lt;/p&gt;
&lt;p&gt;A &lt;code&gt;city → town → village&lt;/code&gt; fallback chain handles the fact that Nominatim doesn&#39;t always return a &lt;code&gt;city&lt;/code&gt; field, depending on how rural the position is.&lt;/p&gt;
&lt;h2&gt;The Mastodon bot&lt;/h2&gt;
&lt;p&gt;I created a separate Mastodon account &lt;code&gt;@waarismax@social.schoonens.nl&lt;/code&gt; on my self-hosted instance and connected it to Home Assistant.&lt;/p&gt;
&lt;h2&gt;The automations&lt;/h2&gt;
&lt;p&gt;Two automations watch &lt;code&gt;sensor.max_jet_status&lt;/code&gt; and post to the bot when it changes. A &lt;code&gt;for:&lt;/code&gt; duration of 5 minutes debounces brief signal dropouts. One automation handles takeoff (offline to airborne), one handles landing (airborne to offline).&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Building a network monitoring dashboard in Home Assistant</title>
    <link href="https://camiel.schoonens.nl/lab/homeassistant-network-monitoring/" />
    <updated>2026-05-15T00:00:00Z</updated>
    <id>https://camiel.schoonens.nl/lab/homeassistant-network-monitoring/</id>
    <content type="html">&lt;p&gt;I run a full UniFi stack at home, a Cloud Gateway Fiber Gateway (UCG), five different switches, and five access points spread across the house. The UniFi integration for Home Assistant gives you device status and connected client counts, but it doesn&#39;t expose bandwidth data as sensors. I wanted to see real-time throughput on my HA dashboard. This post documents how I built that.&lt;/p&gt;
&lt;h2&gt;Why SNMP&lt;/h2&gt;
&lt;p&gt;My first instinct was to look for a purpose-built integration, but nothing reliable existed for per-AP bandwidth at the time. The UniFi switches (USW Flex 2.5G) don&#39;t support SNMP at all, which ruled out the obvious approach of polling switchport counters. The gateway and access points do support SNMP though, which turned out to be enough.&lt;/p&gt;
&lt;p&gt;SNMP is the right approach as every UniFi device keeps internal byte counters per interface. By polling those counters periodically, you can calculate throughput as a delta over time. The performance impact is negligible, a few hundred bytes per query over UDP, even at 10-second intervals.&lt;/p&gt;
&lt;h2&gt;Setting up SNMP&lt;/h2&gt;
&lt;p&gt;SNMP is disabled by default on the UCG. Enable it under &lt;strong&gt;Settings &amp;gt; System &amp;gt; SNMP&lt;/strong&gt; in the UniFi console, set version to SNMP v2c, and note your community string.&lt;/p&gt;
&lt;p&gt;Before adding any sensors to Home Assistant, I used &lt;code&gt;snmpwalk&lt;/code&gt; from my Mac to identify the right interface:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;snmpwalk -v2c -c homelab 192.168.1.1 1.3.6.1.2.1.2.2.1.2
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This returns all interfaces the device knows about. For my UCG, the WAN uplink is &lt;code&gt;eth6.701&lt;/code&gt;. VLAN 701 is what my ISP uses. The interface index was 21.&lt;/p&gt;
&lt;h2&gt;The sensor pipeline&lt;/h2&gt;
&lt;p&gt;Getting from raw SNMP data to a Mbit/s reading in Home Assistant takes three layers:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1. SNMP sensors&lt;/strong&gt; — raw cumulative byte counters, tagged &lt;code&gt;state_class: total_increasing&lt;/code&gt; so HA handles resets correctly.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2. Derivative sensors&lt;/strong&gt; — calculate the rate of change in octets per second. A &lt;code&gt;time_window&lt;/code&gt; of 30 seconds smooths out polling jitter without introducing too much lag.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;3. Template sensors&lt;/strong&gt; — convert octets/s to Mbit/s (octets × 8 / 1,000,000). A &lt;code&gt;| max&lt;/code&gt; with 0 prevents negative spikes when the derivative briefly dips below zero.&lt;/p&gt;
&lt;p&gt;I went through a few failed approaches before landing on this pipeline. My first attempt used template sensors that stored the previous value as an attribute and calculated the delta themselves. This fails because HA evaluates &lt;code&gt;current&lt;/code&gt; and &lt;code&gt;previous_value&lt;/code&gt; in the same render cycle, so the delta is always zero. The &lt;code&gt;derivative&lt;/code&gt; platform solves this cleanly by doing the math in HA&#39;s core rather than in a template.&lt;/p&gt;
&lt;h2&gt;The dashboard&lt;/h2&gt;
&lt;p&gt;For the WAN graph I used &lt;a href=&quot;https://github.com/kalkih/mini-graph-card&quot;&gt;mini-graph-card&lt;/a&gt;. It&#39;s cleaner than apexcharts for simple time-series and doesn&#39;t need a formatter function to display units.&lt;/p&gt;
&lt;p&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://camiel.schoonens.nl/img/ZvuIojRDQo-400.webp 400w, https://camiel.schoonens.nl/img/ZvuIojRDQo-800.webp 800w, https://camiel.schoonens.nl/img/ZvuIojRDQo-3384.webp 3384w&quot; sizes=&quot;100vw&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://camiel.schoonens.nl/img/ZvuIojRDQo-400.jpeg&quot; alt=&quot;Network monitoring dashboard&quot; width=&quot;3384&quot; height=&quot;1860&quot; srcset=&quot;https://camiel.schoonens.nl/img/ZvuIojRDQo-400.jpeg 400w, https://camiel.schoonens.nl/img/ZvuIojRDQo-800.jpeg 800w, https://camiel.schoonens.nl/img/ZvuIojRDQo-3384.jpeg 3384w&quot; sizes=&quot;100vw&quot;&gt;&lt;/picture&gt;&lt;/p&gt;
&lt;h2&gt;Data usage tracking&lt;/h2&gt;
&lt;p&gt;Once the SNMP sensors were in place, adding daily and monthly data counters was straightforward using &lt;code&gt;utility_meter&lt;/code&gt;. I added template sensors on top to convert octets to GB for display, since raw octet values in the billions are not useful on a dashboard card.&lt;/p&gt;
&lt;h2&gt;A note on entity IDs&lt;/h2&gt;
&lt;p&gt;One thing that caused me grief: when migrating from the legacy &lt;code&gt;platform: template&lt;/code&gt; syntax to the modern &lt;code&gt;template:&lt;/code&gt; block syntax (required since HA 2026.6), entity IDs change. The fix: always set &lt;code&gt;unique_id&lt;/code&gt; on every sensor. HA uses the unique_id as the stable internal identifier, so renaming via the UI won&#39;t break references.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Building a travel website with Jekyll and Cloudflare Pages</title>
    <link href="https://camiel.schoonens.nl/lab/Pura-Vida/" />
    <updated>2026-05-04T00:00:00Z</updated>
    <id>https://camiel.schoonens.nl/lab/Pura-Vida/</id>
    <content type="html">&lt;p&gt;Over the past few days, I worked with Claude to build a travel website from scratch for our upcoming family trip to Costa Rica this summer. The result is &lt;a href=&quot;https://puravidareis.nl&quot;&gt;puravidareis.nl&lt;/a&gt;, a Jekyll-based site using the Chirpy theme, hosted on Cloudflare Pages with automatic deployments triggered by Git pushes.&lt;/p&gt;
&lt;p&gt;To round out the static site stack, I added Cloudflare R2 for image hosting, Matomo for privacy-friendly analytics, and an RSS-to-Mastodon pipeline that automatically sends toots when new posts go online. The workflow is straightforward: I write a post on my iPhone or iPad via &lt;a href=&quot;https://workingcopy.app&quot;&gt;Working Copy&lt;/a&gt;, push it to GitHub, and Cloudflare builds and deploys the website in under 60 seconds.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>My photo cleanup workflow</title>
    <link href="https://camiel.schoonens.nl/lab/Photo-Purge/" />
    <updated>2026-04-25T00:00:00Z</updated>
    <id>https://camiel.schoonens.nl/lab/Photo-Purge/</id>
    <content type="html">&lt;p&gt;I have 25 folders (filled with many subfolders) filled with photos from 2001 to 2026. All the folders are stored on my NAS. When I import new photos from my DSLR into Lightroom, I automatically back up the photos to my NAS drive. For pictures taken on the four iPhones in our household, I back up the images from iCloud to my NAS, usually once a year. The result of this process? There are thousands of JPEG files in these folders, and since 2023 the number of pictures we take has increased exponentially.&lt;/p&gt;
&lt;p&gt;I&#39;ve long wanted to clean up this digital mess by removing duplicate photos and selecting one photo from a series of photos. The purpose of all this is not to save disk space; it&#39;s to create physical photo albums for the years since we have children and are a family.&lt;/p&gt;
&lt;p&gt;I&#39;ve made use of the following two macOS apps: PhotoSweeper and ExifRenamer. This is my approach:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Create a zip file of every single year folder for backup purposes. Upload these to Backblaze, my off-site S3 backup solution.&lt;/li&gt;
&lt;li&gt;Copy a single folder with all subfolders from my NAS to my desktop computer, mainly for performance reasons while processing, but also to create a working folder without touching the files on my NAS.&lt;/li&gt;
&lt;li&gt;Have PhotoSweeper analyze the contents of the year folder and subfolders. My desktop computer is an M4 Mac Mini, and PhotoSweeper is incredibly fast at this.&lt;/li&gt;
&lt;li&gt;Manually go through three types of comparisons PhotoSweeper does: filename and EXIF comparison, series comparison for images taken within one minute of each other, and having the app&#39;s algorithm handle image comparison by looking at the images.&lt;/li&gt;
&lt;li&gt;Mark all the images I want to delete based on the analysis, following PhotoSweeper&#39;s advice about which one to delete 9 out of 10 times.&lt;/li&gt;
&lt;li&gt;Let PhotoSweeper delete all the marked photos.&lt;/li&gt;
&lt;li&gt;I&#39;m now left with a cleaned-up annual folder with no more duplicate photos or burst series.&lt;/li&gt;
&lt;li&gt;The final processing step is to have ExifRenamer go through the folders and move the individual JPEG images to a new folder while updating the filenames based on the EXIF data. I name the files with the following syntax: &lt;code&gt;yyyymmdd hhmmss – (Camera Name).jpg&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The result is a cleansed annual archive of images sorted alphabetically in the file system.&lt;/li&gt;
&lt;/ol&gt;
</content>
  </entry>
  <entry>
    <title>Travelling with the Ubiquiti UniFi Travel Router</title>
    <link href="https://camiel.schoonens.nl/lab/Unifi-Travel-Router/" />
    <updated>2026-04-22T00:00:00Z</updated>
    <id>https://camiel.schoonens.nl/lab/Unifi-Travel-Router/</id>
    <content type="html">&lt;p&gt;I run a full UniFi stack at home. I have a UniFi Cloud Fiber gateway, multiple switches and APs, a doorbell and a set of Protect cameras covering my property. UniFi is my go-to brand for networking and security. Yes, it&#39;s expensive, but the convenience of a product that works is worth it, if you ask me.&lt;/p&gt;
&lt;p&gt;Before picking up the UniFi Travel Router (UTR), my mobile networking setup was already solid and secure. I have dedicated WireGuard VPN connections on every mobile device in my household that auto-connect based on SSID identification. It worked fine and it still does. The UTR is not a fix for a broken setup, it&#39;s the elimination of the friction of having to connect to public wifi on multiple devices.&lt;/p&gt;
&lt;p&gt;Until today, I had a setup with per-device WireGuard installations and an SSID-based auto-connecting VPN. It is a legitimate solution that served me well for years. But it also has a consistent pain point: public wifi captive portals. Every device needs to hit the portal individually before the tunnel can come up. This is time-consuming and sometimes not even possible when hotels or airlines limit the number of devices you can connect.&lt;/p&gt;
&lt;p&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://camiel.schoonens.nl/img/htijlDYlpg-400.webp 400w, https://camiel.schoonens.nl/img/htijlDYlpg-800.webp 800w, https://camiel.schoonens.nl/img/htijlDYlpg-1648.webp 1648w&quot; sizes=&quot;100vw&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://camiel.schoonens.nl/img/htijlDYlpg-400.jpeg&quot; alt=&quot;UniFi Travel Router&quot; width=&quot;1648&quot; height=&quot;2914&quot; srcset=&quot;https://camiel.schoonens.nl/img/htijlDYlpg-400.jpeg 400w, https://camiel.schoonens.nl/img/htijlDYlpg-800.jpeg 800w, https://camiel.schoonens.nl/img/htijlDYlpg-1648.jpeg 1648w&quot; sizes=&quot;100vw&quot;&gt;&lt;/picture&gt;&lt;/p&gt;
&lt;p&gt;The UTR collapses that entirely. The UTR connects to the hotel Wi-Fi. You sign in to the captive portal via the UniFi app, and that&#39;s it. Once the UTR is online it sets up the WireGuard tunnel to my Cloud Fiber Gateway at home and I&#39;m ready to go. Every device behind the UTR is online and tunneled immediately.&lt;/p&gt;
&lt;p&gt;The UTR is a real travel device. It&#39;s small and runs on just 5W, so any USB port works. The device has two USB-C ports (one for power and one for tethering) and two GbE RJ45 ports for WAN and LAN connections. You can configure the WAN port as an additional LAN port if needed. The case does get warm under load. The radio stack is Wi-Fi 5, which is dated but, in practice, not a bottleneck for day-to-day usage.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Home Assistant Bot tracks planes flying over my house</title>
    <link href="https://camiel.schoonens.nl/lab/homeassistant-flightradar-bot/" />
    <updated>2026-04-21T00:00:00Z</updated>
    <id>https://camiel.schoonens.nl/lab/homeassistant-flightradar-bot/</id>
    <content type="html">&lt;p&gt;I have a small &lt;a href=&quot;https://social.schoonens.nl/@homeassistant&quot;&gt;Home Assistant Bot&lt;/a&gt; running on my own Fediverse instance. It sends me private messages about what&#39;s happening around the house. At some point I thought: why stop at lights and energy monitoring when I can do more fun things by connecting different APIs?&lt;/p&gt;
&lt;p&gt;So I connected the bot to Flight Radar.&lt;/p&gt;
&lt;h2&gt;What it does&lt;/h2&gt;
&lt;p&gt;Whenever a plane flies over my house on its approach to Amsterdam Schiphol, the bot sends me a (Dutch) private message on Mastodon with all the details:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The airline and flight number&lt;/li&gt;
&lt;li&gt;Origin and destination&lt;/li&gt;
&lt;li&gt;The altitude at which it&#39;s passing&lt;/li&gt;
&lt;li&gt;The expected arrival time at Schiphol&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Why the Fediverse?&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Because I could.&lt;/li&gt;
&lt;li&gt;I already run a Mastodon instance for our household, so the Fediverse was a natural fit. The bot posts directly to my account as a private/direct message, which means no extra apps, no push notification service to maintain — just a message popping up in my timeline like any other. It keeps everything self-hosted and under my control, which fits perfectly with the spirit of both Home Assistant and the Fediverse.&lt;/li&gt;
&lt;/ol&gt;
</content>
  </entry>
</feed>