nuxx.net
Making, baking, and (un-)breaking things in Southeast Michigan.

Category computers

Oops! (Was: Don’t Forget about Bogons…)

GL-MT3000 tethered to an iPhone.

So, after a week I had to revise this post substantially. Originally it was talking about how the bogons list contains private addresses and how in OPNSense this caused problems with using a private network ranged router (like the aforementioned GL-MT3000) as a WAN link caused problems. But then on a quiet Saturday morning a week later I went to reproduce the problem, gather data, and fix it, and… I can’t. So I’m just going to chalk that situation up to me being tired/stressed due to the lengthy power failure or maybe something getting weird/munged/misaligned rule-wise and a tidy-up sorting it out.

I don’t like it, but that happens sometimes. (Get something wrong, go to reproduce, doesn’t work…) What annoys me is went back and forth so many times when it happened that thought I had it right.

But, at least it’s working now, with nothing to fix.

Anyway, here’s the revised post without the now-known-false claims:

After a severe round of early September storms here in Southeast Michigan we’re in the midst of a multi-day power-and-internet outage. While mobile hot spots were kinda working, it was also a pain not having the normal network up, especially as I troubleshot why some Home Assistant devices didn’t come back online. So I set out to up our normal network with a tethered mobile phone as its public internet link instead of the cable modem.

This was super-easy to do, in general, using the aforementioned GL-MT3000 reset to defaults†, connected to an iPhone, in basic router mode, and plugged into the WAN port of my OPNsense router (replacing the cable modem). I did this instead of connecting the iPhone directly because OPNsense doesn’t have all the drivers needed loaded by default — changes I didn’t want to make for a temporary solution — and the router is down in the basement where cell phone signal isn’t quite as good.

While I considered setting up multi-WAN with failover mode, this is a really rare occurrence, and I won’t be keeping the phone connected after Wide Open West comes back online, so replacing the cable modem was much cleaner and simpler.

† Thanks to the lovely brownout, my GL-MT3000 would no longer boot. I had to use the Debrick via Uboot process to get it working again. Fortunately it wasn’t actually-dead. And now I’ll have to put back the IPv6 tunnel stuff… But at least that’s documented.

around the housecomputers

Full Dual Stack (IPv4 and IPv6) Connection from Anywhere

GL.iNet Beryl AX (GL-MT3000) as a dual-stack network access point.

I’ve been doing some day job stuff where I need to test a device from a full dual stack (IPv4 and IPv6) residential network connection, but our house has Wide Open West service which is unfortunately only IPv4. While there are a bunch of ways to tunnel the IPv6 half of a connection out to the internet (eg: tunnelbroker.net and my OPNsense router) I’ve previously had issues with path asymmetry (IPv4 going one route, IPv6 going another) making things… quirky… so I didn’t want to do that. I wanted a real dual stack connection but didn’t want to bring another ISP into the house.

I have a GL.iNet Beryl AX (GL-MT3000) “travel router” that I keep around as a just-in-case piece of hardware. They are super useful for if something happens to the main network, I want a VPN gateway for all devices when traveling, or experimentation and testing.

Well, here we are, testing time, and it’s an (almost) perfect platform for building a custom network with full dual stack support that tunnels out via another host with appropriate connectivity; essentially a VPN.

Along the way I ran into a little wrinkle where, after getting the tunnel and everything up, IPv6 didn’t work. It turns out that the firmware on there (not sure if it’s vendor specific, or standard for OpenWRT) enables NAT (IP masquerading) for IPv6 with no control to turn it off. Troubleshooting this took a bit, and a script was developed to ensure the setting stuck across reboots.

ipconfig /all output showing full dual stack connectivity.

After that, it all just worked. I connect to the device’s wireless network and have a full dual stack network, which allowed me to test what was needed. I’ve been working from this connection for a month and it’s fast enough to be indistinguishable from my home connection, yet provides the extra IPv6 stuff I needed to solve a problem. Beyond IPv6, the only real difference between it and my home connection is that it appears to be in Chicago, and I have to pay $5/mo for the VM and am limited to 1000GB of network traffic. That’s both cheap enough to not care about and plenty of traffic.

For some (mostly-AI-generated) step-by-step-ish directions for setting this up, here you go: Linode_and_GL-MT3000_Dual_Stack_VPN_2026-Jul-22.md

These are basically my notes for doing this again in case I have to set such a thing up in the future. The only real important parts are stepping around the IPv6 Masquerade stuff and the script to persist this setting, but I added the other pieces so it’d be more useful in a year or three when I next need to do this but have completely forgotten about it.

And, I guess it also shows off using Caddy to render Markdown to HTML using the same Chroma engine that I’m using for syntax highlighting mentioned in my previous post.

And yes, AI tools were used to troubleshoot the why-didn’t-IPv6-work part of this. (Which was very welcome, because I had the dual stack tunnel working from a Windows box with the official Wireguard client. Figuring out why it wasn’t working on the GL-MT3000 was a bit of a pain.)

computerswork

Chroma Syntax Highlighting via Caddy

Chroma (via Caddy) highlighting ESPHome YAML.

After learning that Chroma is how Caddy renders Markdown into HTML, I started thinking it’d be neat to use Chroma to do viewing with styling/syntax highlighting of any supported files that I share/archive at nuxx.net/files.

As I began investigating this, thinking that I’d have to do some sort of server-side rendering fed back to the webserver, it turned out there is a great hack that made it a lot simpler: Use the built-in Markdown support to render a code block (only) with the contents of each file.

I’m impressed with how this worked out. With some Caddyfile changes to call this, an updated custom file browser, and the HTML template (yes, all AI-assisted development — it came up with this hack) supported files in my archive get a nice VIEW button next to them which opens them in the viewer, or I can link directly to them with a ?viewer argument. Adding an hl= argument will (eg: hl=19 or hl=10-15 or some comma-separated combination thereof) will highlight individual lines. The screenshot above is from this URL: https://nuxx.net/files/esphome/seeed-xiao-esp32s3_onkyo-a-9050_v2.1.0.yaml?viewer&hl=19

This viewer is capped to 1MB files (larger just send the raw file as clicking/direct linking do) and hl is limited to 100 characters, but this should suffice.

Oh yeah, so the how? Well, it’s all balled up with a whole bunch of CSS and site-specific stuff that I really don’t feel like stripping out to use as an example. And I don’t want an AI-generated how-to… so I guess all you get is the knowledge that this can be done and an example of it in practice. Hopefully that’s enough to feed to your agent and see how it can be implemented for you?

A bit of an aside, but as someone who remains quite skeptical of claims of what “AI” can do, I recognize and appreciate the power of LLMs for software development and troubleshooting. Being comfortable with such tools has made it much easier to get stuff done in my personal computer-hobby and day-job work time. Basically, I find that if I understand the inputs, the desired output (the goal), can describe the problem or task to be done, and can check things along the way, with a bit of guidance LLMs are great at filling in the time-sucking middle. This post from geohot really resonates with me and I suggest reading it the other posts/articles it links to: I love LLMs, I hate hype.

computersnuxx.net

Updating the HDMI-CEC and Onkyo RI to Home Assistant Bridge

Chip, laying on Kristen’s lap, in the living room.

A couple years after putting together the HDMI-CEC to Onkyo RI Bridge I decided to give it a bit of an update and subtle name change. There were a couple problems that I wanted to fix:

  • The PR for adding Onkyo RI support to ESPHome (#7117) worked, but it didn’t get much attention, fell behind, and it’d been rejected when it aged out. So, a new ESPHome build couldn’t use it.
  • Occasionally, usually in the middle of the night, the Home Assistant automation keying off of the Apple TV waking up would turn on the lights in the living room. The Apple TV seemed to wake a little bit for a firmware check, and a false positive in my HDMI-CEC watcher would trigger on this, turning on the lights and receiver.
  • Onkyo receivers also send data out the RI port, but I hadn’t tested receiving data from a device.

The name was also changed from “HDMI-CEC to Onkyo RI” to “HDMI-CEC and Onkyo RI” to better reflect that this bridges these two separate things into Home Assistant (HA). Originally I had the device itself turning the receiver on (via RI) after hearing a certain CEC command (no HA needed), but for flexibility I moved this logic into HA. The name change reflects that it doesn’t do the bridging itself and is really two separate functions that just happen to run on the same ESPHome device.

To handle this I broke it down into a few pieces:

  1. Fix/update/resubmit the Onkyo RI support in ESPHome.
  2. Refine what HDMI-CEC commands are considered to be the Apple TV waking up.
  3. Rewrite/improve the ESPHome YAML.
  4. Update Home Assistant (HA) to use these new features.

Again, leaning hard on an AI coding assistant† I started with building an HDMI-CEC sniffer in ESPHome, collecting a few days of traffic on the CEC bus, and built a good understanding of what the communication actually looks like.

The HDMI-CEC sniffer is here: sniffer.yaml (Note: Completely AI generated test code.)

Next I worked with the agent to modernize my previous ESPHome Onkyo RI code for the current version of the platform, tested it in send and receive configurations, and submitted a new PR (#18585). This PR is much more robust than the last one, even including a hand-drawn schematic showing my hardware test setup. I intentionally did this using AI as a programming partner, leaning on it for code logic and consistency and testing, but doing the architecting and validation and and git/GitHub stuff by hand.

Test Transmit and Receive hardware for Onkyo RI integration in ESPHome.

Along the way I took a detour to inspect all commands that came out of the RI port when pressing the buttons on the RC-830S remote that came with the A-9050 receiver. Unfortunately, what I realized is that RI is more complicated than just a remote control command. I thought the RI port might re-transmit what the remote sends, but I was very wrong.

As (in hindsight is obvious), RI is an inter-device protocol for managing a home theater system. The remote is meant to talk to individual device. Supported (in function and expectation) by the RI mode switches in the back, parts of the remote might only trigger RI signals when certain inputs are selected, inputs might vary (or not cause) RI output, or surprisingly some buttons don’t cause anything to be sent. Or others work the same all the time.

Having set out to only update my current hardware to work better, I opted to not add any read-RI-into-HA stuff for now. There’d be a lot of complexity to unwind before it’d be usable, and I don’t have good use for it, so I dropped the idea. But at least the software support is there and tested.

(You can download the Onkyo RI transmit and receive tests for ESPHome here: ri-transmit-test.yaml / ri-receive-test.yaml. Disclaimer: These are both completely AI generated development test harnesses.)

With these two pieces — new understanding of HDMI-CEC and a freshened-up Onkyo RI component — I was then able to build a new config for the Seeed Studio XIAO ESP32S3-based hardware I already have in place. And along the way a previous bug I’d encountered with Over-The-Air (OTA) updates to ESPHome failing on this module got fixed, which is a nice side effect. Now it’ll be much easier to try out changes.

I’m pretty happy with this. The result is an expanded/modernized device that nicely communicates with modern (HDMI-CEC) and old (Onkyo RI serial port) home entertainment stuff, logically coupled together with ESPHome and Home Assistant. There’s modern code for RI in HA, and while the PR is still waiting to be merged, I’m hopeful. Going off the test results and AI feedback on the code quality I’m much more hopeful this time.

The updated ESPHome code is here: seeed-xiao-esp32s3_onkyo-a-9050_v2.1.0.yaml

Home Assistant turning on the Onkyo RI receiver and living room light grouping after receiving an HDMI-CEC event.

Currently the typical usage flow is:

  • Push button on Apple TV to wake it.
  • Apple TV wakes Vizio TV.
  • ESPHome sees this happen via HDMI-CEC and informs Home Assistant.
  • Home Assistant turns on the receiver via Onkyo RI and some lights.
  • Then later, when the Apple TV goes to sleep (automatic or manual), ESPHome sees this, notifies HA, and an inverse flow happens.

In the future I might begin looking at waking or sleeping the system remotely by sending HDMI-CEC commands. This eluded me before, but with some automated analysis of traffic on the CEC bus, I might be able to make it happen. Maybe it’d be nice to have the system wake up remotely, perhaps as part of a lets-watch-TV automation? Even if I don’t do this in the end, it would at least be nice to know how…

I might also look at ingesting commands sent out by RI, but this goes beyond the receive-only test and will both require new hardware on the ESP32 side (to handle send and receive on the same pair of wires) and digging into the RI complexity, so… probably less likely.

Using This in Home Assistant

With the HDMI-CEC triggers for the logic being in Home Assistant, and as part of handling the random wakes (receiver/lights turning on), I needed tighter triggers in Home Assistant. Here’s what’s now used used, specific CEC frames:

triggers:
  - trigger: event
    event_type: esphome.hdmi_cec_frame
    event_data:
      source: '4'
      opcode: '82'
      payload: '50:00'
    id: system_on
  - trigger: event
    event_type: esphome.hdmi_cec_frame
    event_data:
      source: '4'
      opcode: '90'
      payload: '01'
    id: system_off

Specifically, this looks for CEC frames that are from source 4 (the Apple TV, a playback device).

The system_on event is the Active Source opcode (82), from physical address 5.0.0.0 (50:00) or the Apple TV’s HDMI port. Or, otherwise stated, a playback device set itself as the active source of content, and is connected to port 5.

The system_off event is the Report Power Status opcode (90), with Standby power status (01): the same playback device went into standby.

This works for my setup, but be aware that it might not work for you.

With things plugged in differently, or possibly with different devices, different addresses are likely needed. For example, if I added another playback device and added it the source could change. If plugged into a different port, the physical address (payload) of the Apple TV would change from 5.0.0.0.

To use this yourself you’ll want to inspect the HDMI-CEC frames (look at turning logger level: to DEBUG) and using esphome logs) to see what’s on your bus before writing the automation. LLMs (AI) are really good at helping with this kind of protocol analysis.


† Claude Opus 5. Even though it’s the results that matter, part of me still feels compelled to mention every time I use AI tools to develop something. Yet I don’t mention using search engines, IDEs, compilers, etc… And yes, this entire blog post, comments in the main ESPHome YAML, and the PR was written by hand, just by me.

around the housecomputerselectronicsmaking things

Vibe Coded Fixes

Working in Claude Code to fix broken image links in this blog.

For all the potential problems that come with vibe coded stuff, generating/composing one-off fix-it scripts can be damned handy. For example, this blog got a big tune-up over the past day: almost 2000 embedded images have been unbroken.

For a lot of years I maintained a personal online photo gallery using Gallery2, but as it hadn’t been maintained since 2012, used a lot of server space, and had a very not-modern feel to it, I shut it down in mid-2020 and made a static archive that I just hosted at home. The biggest problem with doing so was that this blog had a lot of images hotlinked into there, so all those posts broke.

Fixing this manually was a pain, and while I kept a copy of the database+images around, somehow using that to fix things was a Big Task that I never started on.

Enter the modern world of AI-assisted coding and over the past few days of vacation, between riding bikes and walks and such, I prompted Claude to develop a solution. What it generated was a series of scripts that identified all posts containing images hotlinked to the old gallery location, found the related images, uploaded them into the Media Library, then edited each post to make it work.

That’s 1994 blog posts whose images have been restored; things like my Low-Cost Fat Bike Stand post are now working again.

This is where I find a ton of benefit to AI tools such as this: putting together the middle (the code) to fix up problems. If the inputs and desired outputs are known, and one can sanity check the system as it evolves, a lot can get done. And quickly. Tools like this have also been hugely beneficial at work, saving me absurd amounts of time with analyzing log files (eg: network captures, Process Monitor output) where I no longer have to timeline things myself.

I’m past the point where I’m surprised, but I’m still really impressed. This has been a long-standing back-of-my-mind problem/quirk with my site that I’d just sort of accepted… But here we are, it’s fixed. Yay!

computersnuxx.net

local-heatmap-tile-server v1

local-heatmap-tile-server v1 showing Northern Michigan in Warm style and Light appearance.

During a long drive to (and from) Florida, and a lot of thinking about maps, I realized something that I really wanted, and something that I could use AI-assisted development to experiment with: generating a heatmap from all my personal, archived activity files. Specifically, generating XYZ tiles, making them available via TMS (so they can be used as an imagery layer in JOSM), and also displaying them on a slippy map.

For years I’ve been using the Strava heatmap as a layer in JOSM for OpenStreetMap (OSM) editing and this works great, but I’m finding myself disconnecting from online social networks, including Strava, more and more. And while the Strava Global heatmap does work as a data layer with a free account, I began thinking about other options to use it, and other cloud providers, less and less. And yes, there’s similar offerings from RideWithGPS and whatnot, but I really wanted to generate my own since it’d give me a lot more flexibility.

So, for my next project working with Claude, I decided to try building a personal heatmap generation tool. And it worked.

Cool heatmap of my ride and hike data, used as a layer in JOSM. (Ishpeming/Negaunee area.)

Using AI tools to develop software is nothing new, but I’ve never really been one to jump right on brand-new things, instead waiting for them to bake and show their utility before I dig in and use/learn them. I also find it very difficult to learn any tool or system unless I have a way to apply it. But when I do, getting my head around it comes pretty quickly.

In making this I’ve learned / found / finally-realized that with a known set of inputs, a desired output, an ability to identify/recognize bugs, and a task that’s known-possible, AI-assisted development saves can save incredible amount of time. Within reason it makes it possible for me to be more of a product manager than developer. Since I’m not really a developer (my career is in systems management and troubleshooting), that work for me is slow… and I’m not good at it.

Using Claude on the desktop to write the code, VS Code to read and make a few manual edits, and Docker Desktop so I could keep an eye on things, after about a week of free-time iterating, this is what I came up with, and I’m quite pleased:

c0nsumer/local-heatmap-tile-server

This is a single Docker container that uses a bunch of Python to import GPS data files (.FIT, .GPX, .TCX), imports, deduplicates, and renders a complete set of XYZ tiles. It then makes them available via HTTP (for display in a slippy map or something like JOSM) or exports them to a PMTiles file for simple hosting. And it has a built-in slippy map viewer/data manager and a couple bundled viewers for completely static hosting (example).

Dashboard for importing new files, stats, and exporting the heatmap for static use.

The Python webserver, uvicorn, isn’t the fastest nor great at caching, so the XYZ tiles are fronted with nginx to very quickly serve them from disk, only passing the request back to uvicorn and the Python stack for rendering if the tile isn’t present. Once the tiles are rendered they are cached very quickly served up solely by nginx, to the point where panning and zooming freely is seamless. (And yes, you can pre-render all tiles for optimal performance.)

It’s been tested on ~4000 track single-GPX files (exported from rubiTrack), ~4000 .FIT files directly from Garmin devices, and a bunch of different types of single GPX files. And… it seems to work!

The file inputs (FIT, TCX, GPX) aren’t special and parsers have existed for a long time. Nothing about heatmaps is new. Tile rendering isn’t new. Tile serving isn’t new. Nor are web-based heatmaps from fitness tracker data. But it needed to be glued together to get something that works this way, and this type of development made it possible. And I learned something new about AI-assisted software development along the way. It’s sure an interesting new world with these tools.

And yes, beyond thinking about the features I had to do a lot of nudging along the way.

Some major bugs that were encountered were getting cross-tile heatmap brightness correct, missing cross-tile data, tiles not rendering properly when called via different ways, moving to a faster web server so panning the map felt smooth, and a whole lot of tweaking of brightness and line thickness and blur and such at different zoom levels so it’d feel nice to use, noticing and dealing with malformed XML in GPXs…

But this was nudging via prompts and having a bit of an idea what it was doing, not coding. Which is what’s so weird and new to me. It’s like directing a team of pretty-decent junior devs.

And the end result is something I’ve wanted for a while. And now it exists. (And no, none of this post was written by any AI tool.)

computersmaking thingsmapping

Problems Caused By “transition: 0”

Around the time I set up Hue bulbs for the sunrise simulation I began to have a very odd problem with a couple Home Assistant controlled lights. Turns out that problem was caused by turning them off with transition: 0.

In our HA instance I have two helpers, each called Interior Lights, with one being a group of switches and the other a group of lights; both containing only things we’d consider interior lights. I then have an automation that turns both groups off, and trigger it from something like All Interior Lights Off which I’ll commonly trigger before going to bed, when leaving the house, etc.

Because a group entity can only hold the same type of entity, and we have lights that are both lights entities (i.e. bulbs) and switches entities (i.e. smart switches controlling dumb bulbs) we need one group for each type.

The specific problem was that two lights in the group — one being an IKEA TRADFRI 800 lumen bulb and the other BTF-LIGHTING Zigbee single-color LED controller — started acting oddly. At first it was hard to tell what was going on, the IKEA bulb would seem to be on when not expected and the LED strip would be off when it should be on. Eventually I found repeatable cases:

  • When triggering All Interior Lights Off automation, if the IKEA bulb was already off, it’d turn on at minimum brightness.
  • After using All Interior Lights Off, the BTF-LIGHTING LEDs, on next on command, would flicker on and then almost immediately turn off.

It turns out the problem was having transition: 0 on the light group’s off automation. Back when doing tweaking for the Hue bulbs I changed this because otherwise these bulbs would dim out over 1-2 seconds instead of just turning off, and I didn’t like this. Unfortunately, this change exposed some bugs.

So I removed the transition from the automation and poof; no more weird problems.

around the housecomputers

Adobe Illustrator Preventing macOS Sleep?

Ever since getting my new monitor (a Dell U3225QE — a nice IPS LCD after some OLED issues) I’ve been having problems with it not going to sleep. But that’s not usually a monitor problem, especially as I could manually put it to sleep… So what’s keeping macOS from putting it to sleep?

Well, thankfully with pmset one can see what’s going on:

c0nsumer@mini ~ % pmset -g
System-wide power settings:
Currently in use:
standby 0
Sleep On Power Button 1
autorestart 0
powernap 1
networkoversleep 0
disksleep 10
sleep 0 (sleep prevented by backupd-helper, powerd, backupd, coreaudiod, coreaudiod)
ttyskeepawake 1
displaysleep 10 (display sleep prevented by CEPHtmlEngine)
tcpkeepalive 1
powermode 0
womp 1
c0nsumer@mini ~ %

There we go, seems CEPHtmlEngine is preventing the display from sleeping. So what is it?

c0nsumer@mini ~ % pmset -g assertions | grep CEPHtmlEngine
pid 15995(CEPHtmlEngine): [0x00038aae00059926] 46:44:27 NoDisplaySleepAssertion named: "Video Wake Lock"
c0nsumer@mini ~ % ps aux | grep 15995
c0nsumer 15995 3.4 0.1 412316000 64256 ?? R Sat08AM 128:30.24 /Applications/Adobe Illustrator 2025/Adobe Illustrator.app/Contents/MacOS/CEPHtmlEngine/CEPHtmlEngine.app/Contents/MacOS/CEPHtmlEngine b27716d6-c14c-49e4-8612-b5ab9de9bdf4 1103d4a0-8756-40b2-af81-5646ba80756f ILST 29.8.4 com.adobe.illustrator.OnBoarding 1 /Applications/Adobe Illustrator 2025/Adobe Illustrator.app/Contents/Required/CEP/extensions/com.adobe.illustrator.OnBoarding 32 e30= en_US 1 -11316397 0
c0nsumer 58444 0.0 0.0 410724448 1472 s002 S+ 7:14AM 0:00.00 grep 15995
c0nsumer@mini ~ %

Really? Illustrator? Huh… I have been working on a new map of Bloomer Park (in anticipation of the forthcoming Clinton River Oaks Park changes) for CRAMBA and leaving it open in the background… I guess that’s it.

And strangely, closing and re-launching Illustrator made the assertion go away. And now the problem is gone.

Oh, Adobe…

At least it’s easy to tell why it was happening.

(This is Adobe Illustrator v29.8.4 on macOS Sequoia 15.7.3.)

computers

OLED… Not for me.

When I switched from an iMac to a Mac mini in late 2024 I choose an ASUS ProArt 5K PA27JCV (24″, 60 Hz) for the monitor and while it looked great, it died after 14 months, seemingly with a backlight or power supply problem. ASUS’ warranty support requires shipping the monitor back, potentially waiting 3-4 weeks, and then getting a replacement. And worse, the replacement could have dead pixels, as the ASUS warranty doesn’t consider ≤5 dark pixels a problem.

The old HP ZR2440w that I swapped in as a spare wasn’t cutting it, so with an indeterminate wait ahead of me, potentially receiving something with bad pixels, and my being vaguely interested in something larger and with a faster refresh rate I went looking at new monitors.

Coming to the realization that 4K is probably fine I picked up a Dell 32 Plus 4K QD-OLED Monitor – S3225QC from Costco for $499. It was well reviewed online and looked pretty good when I played with one for about 20 minutes at Micro Center. When I got home and sat in front of it doing my normal things it looked a bit… different… almost as if my glasses weren’t working quite right. But I figured new monitor tech just needed some time for me to get accustomed to. After all, it had a very high contrast ratio and sharp pixels; maybe it’s just that?

After a few days it still didn’t feel right, so I began looking for a solution. Costco has a 90-day return window for computer monitors, so I had some time, but this didn’t look good; I wanted an answer soon.

I was fortunate to be able to borrow a Dell UltraSharp 32 4K USB-C Hub Monitor U3223QE for the weekend, which was perfect as being a being a high end display with the same resolution and panel size as the S3225QC I could compare them side by side. And in the end the LCD just looked better.

I took some macro photos of both displays and it turns out that what was bothering me was fringing, a problem common to OLEDs. It was hard to point out during normal use other than text-is-a-bit-blurry-and-weird , or like an oversharpened image, or almost like artifacted text in a JPEG image, but with photos it was much easier to see what’s going on. And better, the cause: the arrangement of the subpixels; the little red/blue/green dots that make up a pixel.

As shown above, the subpixles in the Dell S3225QC QD-OLED form a square with green on the top, a larger red pixel in the lower left, and smaller blue in the lower right. The Dell U3223QE, a typical LCD, has three vertical stripes making a square. The result being that high contrast edges look very different on an OLED, often with a strong off-color border — or fringe — along horizontal and vertical lines.

In the photos above, note the vertical part of the 1 which has red and green dots along its right side, and large red dots along the top of the 6 with green along the bottom. These are the strongly colored fringes. (On the LCD they appear white as the three equal size subpixels pixels act equally.)

This meant that things that I tend to do, text or fine lines in maps or CAD-type drawing, are not right at all on the pixel pattern found in this OLED panel. Beyond the pixel pattern, I also suspect that the much crisper pixels (defined points of light) contribute to the fringing having an artifacting-like effect.

This was much more pronounced when looking at light text on a dark background; the way that I read most websites. Visual Studio Code does a wonderful job demonstrating this problem:

This gets at why OLEDs make great TVs and gaming monitors. The contrast is outstanding, color is excellent, and high refresh rates are ideal for moving images and fast-response games. And there’s no noticeable fringing because edges are constantly moving across pixels; almost nothing is still. They also work great on small devices like phones where the pixel density is so high that fringing is too small to see.

But on desktop monitors for still things — text and fine lines — OLEDs currently just aren’t great; I guess that’s why office and productivity type monitors are still LCDs. Even though I don’t like being that person who returns computer stuff just because they don’t like it, I ended up returning the monitor after only four days of using it. The S3225QC and it’s QD-OLED just doesn’t work for me; it made my eyes feel funny to use.

Within the past few weeks LG has announced RGB stripe OLED panels which will resolve this problem, but there aren’t currently any monitors available using these panels, so back to an LCD I’ll go. (It looks like ASUS and MSI will some them available soon, but only as wide-screen gaming monitors. And I suspect the first ones available will be fairly expensive.)

Whether this’ll be buying my own U3223QE, perhaps a Dell U3225QE (adds 120 Hz scanning, an ambient light sensor, and a Thunderbolt dock), or just waiting for an ASUS PA27JCV to come back, I’m not sure… But whatever I end up using will, for now, will be an LCD, not an OLED.

computers

Home Assistant as Personal Device Tracker

Last two years of my phone’s location, as gathered by Home Assistant.

Part of our Home Assistant (HA) setup uses the Companion Mobile App for easy remote control and to collect data from our devices. The main tracked item is the phone’s location, so HA can tell if we’re home or not, and currently I only use it to change how some lighting automations work.

I also have HA set up to log all device state data (switches, outlets, climate sensors, power consumption) to a local instance of InfluxDB, and then have Grafana installed so I can visualize this data.

Mains Voltage via Home Assistant from February 2024 through December 2025.

My original use for this was long-term logging of temperature and humidity sensor data — which is neat to see — but as I’ve experimented with graphing things like mains voltage. This was neat because it made it easy to see things like how voltage drops and becomes erratic during summertime cooling periods. And showed that grid voltage jumped up by ~2VAC in March 2025, around which time I recall DTE doing utility work on the grid just north of our house. (Yes, evidence of them improving things locally.)

Late on Christmas evening, wanting some time to just sit alone and do things, I put together a map showing where my phone had been. I’m pretty happy with how it came out, as I can now input a time range and dots will appear for each logged location, color-coded with geopositioning accuracy (brighter green is more accurate).

I also used this as another exercise in working with LLM tools like ChatGPT. I’m (finally?) realizing how useful this can be when thought of as a modern search engine. There’s still constant reminders of how imperfect and problematic results can be, but with a domain background it’s helpful. I find that thinking of these tools as tireless (yet emotionless) junior employee who makes lots of mistakes and needs all responses tested and vetted works… decently… in pointing me in a decent direction.

But I digress… Here’s the query that’s the main point of this and makes it all go:

SELECT "latitude", "longitude","gps_accuracy"
FROM "homeassistant"."autogen"."state"
WHERE "entity_id" = 'pixel_8'
AND $timeFilter
AND "gps_accuracy" < 100
Last 30 days of phone location data.

It was then simply a matter of putting this into a Geomap that displays a point for each location, and colors it based on gps_accuracy state and looks decent. I was even able to place it all on the Thunderforest Landscape map tiles which shows OSM-mapped trails and has been oh-so-useful on my RAMBA Trails Map.

Initially I looked at a heat map, but it didn’t seem as useful as individual points. I may explore this later, but the device where I’m currently running HA is a bit under-powered for this. And note that the query above excludes records that have a GNSS accuracy worse than 100 meters as this generally means that GPS (et al) wasn’t working at all and geopositioning likely came from local mobile towers (which shows me as being on tall local buildings, in fields I’d never visit, etc).

While obvious in retrospect, the most notable things this shows me is that when I’m driving — typically running OsmAnd+ or Google Maps (or both) — the recorded points are high accuracy and frequent. When riding my bike, carrying my phone idly in a pocket, the GNSS sensor is likely PRIORITY_PASSIVE so the dots are both infrequent and low accuracy.

It’s also just neat to look at. Things jump out like a trip to IKEA in Canton, riding at Island Lake and Holly Wilderness, etc.

I’m curious to see what I can further tease out of the logged data. The HA Companion mobile app can get all sorts of interesting info via its Sensors. For example, the Activity Sensors on iOS automatically detect:

  • Stationary
  • Walking
  • Running
  • Automotive
  • Cycling

And on Android:

  • in_vehicle
  • on_bicycle
  • on_foot
  • running
  • still
  • tilting
  • walking

Plus there’s things like what’s being done with the device, what’s seen about its environment (including visible wireless networks, Bluetooth devices), etc…

It might be neat to see what more I can get out of this. Or it might just end up as a nudge to decrease what HA is collecting (and possibly purge some of it from the db).

Of course, it pales in comparison to what the telcos, device manufacturers, OS vendors, and app vendors can do with their data engineers, massive troves of data and ability to cross-reference, etc. (A bit of a reminder that phones are just behavior-trackers that also make calls and take pictures…)

I hope to soon try migrating this HA instance from a Raspberry Pi 4B to a higher-powered slim PC. While I don’t intend to take this much further, it will provide more power for chewing on data like this and will hopefully let me figure out a disaster recovery plan for HA that includes preserving all logged data. When first setting up this map I tried to draw both a location and heatmap and this was a little too much for the Pi and as it ground to a halt Kristen noticed that the back yard lights weren’t turning on properly. Doh! Or I guess I could just do the processing on another machine…

computers