<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
    <channel>
        <title>Tech Journal</title>
        <link>https://sobesto.com</link>
        <description>Thoughts on web development, tooling, and the craft of code.</description>
        <language>en</language>
        <atom:link href="https://sobesto.com/feed" rel="self" type="application/rss+xml" />
        
        <item>
            <title><![CDATA[Turning a Forgotten BlinkStick Into an Ambient Weather Display]]></title>
            <link>https://sobesto.com/blog/blinkstick-weather-display-with-home-assistant</link>
            <guid isPermaLink="true">https://sobesto.com/blog/blinkstick-weather-display-with-home-assistant</guid>
            <pubDate>Mon, 09 Mar 2026 00:00:00 +0000</pubDate>
            <description><![CDATA[A BlinkStick Square was gathering dust in a drawer. I wrote a Home Assistant integration for it and wired it up to the weather forecast — now a single glance at the LED tells me whether to grab a coat.]]></description>
            <content:encoded><![CDATA[<h2>The Drawer Find</h2>
<p>I was tidying up my desk last week and pulled open the drawer where old tech goes to die. Tangled cables, a Pi Zero I bought for a project I never started, and a <a href="https://www.blinkstick.com/products/blinkstick-square">BlinkStick Square</a> — a tiny USB board with eight RGB LEDs on it. I'd bought it years ago, played with it for an afternoon, and forgotten about it.</p>
<p>I plugged it into my Raspberry Pi 5 running Home Assistant, half expecting nothing to happen. The LEDs lit up white. Still alive.</p>
<p>Home Assistant used to have a BlinkStick integration, but it was removed a while back as part of a cleanup of unmaintained components. That meant no config flow, no UI, nothing. If I wanted this thing to do something useful, I'd have to write the integration myself.</p>
<h2>Building the Integration</h2>
<p>The BlinkStick hardware is straightforward. It's a USB HID device — no serial port, no special drivers. The Python <code>blinkstick</code> library handles the low-level communication: set a color, set brightness, run an effect. The challenge was wrapping that in a proper Home Assistant component with config flow, device discovery, and a clean light entity.</p>
<p>I set up the integration as a custom component with a few goals:</p>
<ul>
<li><strong>Standard light entity</strong> — full RGB color picker, brightness slider, works with any automation</li>
<li><strong>Effects</strong> — Pulse, Morph, and Blink, selectable from the effects dropdown like any other HA light</li>
<li><strong>Config flow</strong> — add via UI, no YAML needed, with USB auto-discovery for plug-and-play setup</li>
<li><strong>Multiple devices</strong> — each BlinkStick is identified by serial number, so you can have several</li>
</ul>
<p>The trickiest part was the effects. HA light entities expect <code>turn_on</code> to return quickly, but effects like Pulse run continuously. I ended up running effects in background threads with a threading event for clean cancellation — when you change the color or turn off the light, the running effect stops immediately without any awkward delay.</p>
<h2>The Weather Idea</h2>
<p>A working LED is nice, but a working LED that does nothing useful is just a night light. I wanted it to tell me something at a glance — something I check every morning anyway.</p>
<p>Weather.</p>
<p>The concept is simple: the LED color represents the current weather condition, and the brightness represents how the temperature compares to what's normal for this time of year. One look, two pieces of information.</p>
<h2>Color Mapping</h2>
<p>Each weather condition maps to a color:</p>
<ul>
<li><strong>Green</strong> — sunny or clear. Good day ahead.</li>
<li><strong>Yellow</strong> — partly cloudy or overcast. Not bad, not great.</li>
<li><strong>Purple</strong> — fog. Wrap up, drive carefully.</li>
<li><strong>Blue</strong> — rain. Grab an umbrella.</li>
<li><strong>White</strong> — snow or sleet. Layer up.</li>
<li><strong>Red</strong> — lightning or thunderstorm. Maybe stay in.</li>
<li><strong>Orange</strong> — windy. Hold onto your hat.</li>
</ul>
<p>After living with it for a few days, I found I stopped thinking about the colors consciously. Purple in the morning just meant &quot;it's foggy&quot; without reading anything or pulling out my phone.</p>
<h2>Brightness as Temperature</h2>
<p>Color alone doesn't tell you whether it's a mild 15 degrees or a bitter 2 degrees. That's where brightness comes in.</p>
<p>The automation compares the current temperature against UK monthly averages — 4 degrees in January, 20 in July, and so on. If the temperature is well above average, the LED is bright. Well below, it's dim. Around normal, it sits in the middle.</p>
<p>In practice: a bright green LED in March means an unusually warm sunny day. A dim blue means rain and cold — colder than you'd expect for the month. The brightness gives context that color alone can't.</p>
<p>The thresholds work out to roughly:</p>
<ul>
<li><strong>Very bright</strong> — more than 5 degrees above the seasonal average</li>
<li><strong>Bright</strong> — 2 to 5 above</li>
<li><strong>Medium</strong> — within a couple of degrees either way</li>
<li><strong>Dim</strong> — 2 to 5 below</li>
<li><strong>Very dim</strong> — more than 5 below</li>
</ul>
<h2>The Dashboard</h2>
<p>An LED on a desk is fine for me, but the rest of the household wanted to know what the colors meant without memorising a table. So I built a small dashboard setup in Home Assistant.</p>
<p>The main view has a compact markdown card that shows the current state in one line:</p>
<p><strong>BlinkStick</strong> Purple -- Medium / 6.8 degrees vs 8 degrees seasonal avg</p>
<p>Below that sits a native weather card showing the full forecast, so you get both the at-a-glance LED summary and the detailed breakdown in the same view.</p>
<p>Tapping &quot;legend&quot; opens a subview with three panels: a color-to-condition table on the left, a brightness-to-temperature table on the right, and the monthly seasonal averages along the bottom. I used card-mod to strip the table borders and stretch everything to full width — it took a few rounds of fighting with shadow DOM styles, but the end result is clean and readable. The card YAML is included in the repo README if you want to replicate it.</p>
<h2>Sharing It</h2>
<p>The integration itself is a custom component installable via HACS: <a href="https://github.com/azgooon/ha-blinkstick">ha-blinkstick</a>. It works with any BlinkStick model — Square, Strip, Pro — anything with the standard USB vendor and product ID.</p>
<p>The weather automation is packaged as a Home Assistant Blueprint, so you can import it with one click from the repository README. It takes three inputs: your weather entity, your BlinkStick light entity, and optionally your own monthly temperature averages if you're not in the UK.</p>
<h2>Was It Worth It?</h2>
<p>Honestly, yes. Not because the world needed another weather display, but because it turned a forgotten gadget into something I actually look at every day. It sits on my desk, quietly glowing, and I know what to expect outside before I open the curtains.</p>
<p>If you've got a BlinkStick in a drawer somewhere, dust it off. It takes about ten minutes from plugging it in to having a working weather display.</p>
]]></content:encoded>
            <category><![CDATA[Home Assistant]]></category><category><![CDATA[IoT]]></category>
        </item>
        
        <item>
            <title><![CDATA[Fixing Frozen HK Citation Speakers with Home Assistant]]></title>
            <link>https://sobesto.com/blog/fixing-frozen-hk-citation-speakers-with-home-assistant</link>
            <guid isPermaLink="true">https://sobesto.com/blog/fixing-frozen-hk-citation-speakers-with-home-assistant</guid>
            <pubDate>Thu, 26 Feb 2026 00:00:00 +0000</pubDate>
            <description><![CDATA[Harman Kardon Citation One speakers sound fantastic but freeze regularly. Here's how I traced the problem, built a Home Assistant integration to detect it, and automated the fix with smart plugs.]]></description>
            <content:encoded><![CDATA[<h2>The Setup</h2>
<p>Last year my partner and I both picked up Samsung phones. Each came with a free <a href="https://www.harmankardon.com/citation-one.html">Harman Kardon Citation One</a> speaker as part of a promotion. We set them up, connected Google Assistant, and were genuinely impressed. The sound quality for a compact speaker is excellent — warm, balanced, and loud enough to fill a room.</p>
<p>Within a week I'd ordered three more. Kitchen, hallway, back bedroom, master bedroom, living room — music and voice control everywhere. For the price, it felt like a steal.</p>
<h2>The Problem</h2>
<p>The joy lasted about four days.</p>
<p>We started noticing that speakers would randomly stop responding to voice commands. You'd say &quot;Hey Google, what's the weather?&quot; and instead of a helpful answer, you'd get:</p>
<ul>
<li><em>&quot;Something went wrong. Try again in a few seconds.&quot;</em></li>
<li><em>&quot;Sorry, try logging in via the Google Home app.&quot;</em></li>
<li><em>&quot;There was a glitch. Try again in a few seconds.&quot;</em></li>
</ul>
<p>The strange part: Bluetooth streaming still worked fine. Spotify Connect still worked. The speaker wasn't crashed — Google Assistant was just... frozen. The only fix was to physically unplug the speaker, wait a few seconds, and plug it back in.</p>
<p>With five speakers, this became a daily chore. Someone would walk into the kitchen, try to ask for a timer, get the frozen response, sigh, and pull the plug. Not exactly the smart home dream.</p>
<h2>Digging Deeper</h2>
<p>I run <a href="https://www.home-assistant.io/">Home Assistant</a> on my home network, so I had a decent starting point for investigation. The Citation One speakers run a variant of Google's Cast platform, which exposes a local HTTP API on port 8008 and an HTTPS service on port 8443.</p>
<p>I started poking at both ports on a known-frozen speaker versus a healthy one. The difference was immediate and consistent.</p>
<p><strong>Healthy speaker (Master bedroom):</strong></p>
<pre><code>POST /setup/get_app_device_id  →  77ms
POST /setup/reboot             →  31ms
GET  https://:8443/eureka_info →  45ms
</code></pre>
<p><strong>Frozen speakers:</strong></p>
<pre><code>Kitchen:      get_app_device_id=5000ms (timeout!)  reboot=42ms
Back bedroom: get_app_device_id=2544ms             reboot=53ms
Hallway:      get_app_device_id=38ms               reboot=3106ms
</code></pre>
<p>Three symptoms, and here's the interesting bit — different speakers exhibited different ones. The Kitchen speaker was slow on <code>get_app_device_id</code> but fast on <code>reboot</code>. The Hallway speaker was the opposite. A single-endpoint check would have missed half of them.</p>
<p>I also discovered a third indicator: port 8443, the HTTPS service that powers Google Assistant's cloud communication. On a frozen speaker, it hangs completely — the connection times out after a few seconds. On a healthy speaker, it responds instantly. This turned out to be the most reliable signal of all, because some frozen speakers still responded normally on port 8008.</p>
<h2>Building the Detection</h2>
<p>Armed with three reliable symptoms, I built a Home Assistant custom integration: <a href="https://github.com/azgooon/ha-hk-citation">ha-hk-citation</a>.</p>
<p>It does three things every five minutes:</p>
<ol>
<li><strong>Discovers speakers</strong> via mDNS — scans for <code>_googlecast._tcp.local.</code> services and filters to HK Citation models</li>
<li><strong>Probes each speaker</strong> with three checks — two POST requests on port 8008 (measuring response time) and one HTTPS request on port 8443 (checking for timeout)</li>
<li><strong>Exposes a binary sensor</strong> per speaker — <code>Connected</code> means healthy, <code>Disconnected</code> means frozen</li>
</ol>
<p>No configuration needed beyond clicking &quot;Add Integration&quot;. Speakers appear automatically as they're discovered on the network. If a speaker gets a new IP address from DHCP, the integration handles it transparently — your automations don't break.</p>
<h2>The Automation</h2>
<p>Detection is only half the solution. The other half is doing something about it.</p>
<p>I ordered five <a href="https://www.aliexpress.com/item/1005006346571151.html">Sonoff MINIR4M</a> smart switches — one for each speaker's mains plug. They're tiny enough to fit behind the wall socket, run on Wi-Fi, and integrate natively with Home Assistant.</p>
<p>Now the clever part. You don't want to reboot a speaker the instant it freezes. These speakers play a boot chime when they restart, and if someone's asleep or in a meeting, that's not ideal. So the automation looks like this:</p>
<ul>
<li><strong>Trigger:</strong> <code>binary_sensor.kitchen_speaker_health</code> turns to <code>Disconnected</code></li>
<li><strong>Conditions:</strong> It's daytime (8am–10pm) AND nobody is home (presence detection via phone GPS)</li>
<li><strong>Action:</strong> Turn off the smart plug, wait 10 seconds, turn it back on</li>
</ul>
<p>If the speaker freezes at night, it waits until morning. If someone's home, it waits until they leave. The speaker reboots at a convenient time, and by the time anyone tries to talk to it, it's already back and responsive.</p>
<h2>The Result</h2>
<p>After running this setup for a couple of weeks, the experience is night and day. Every speaker responds when spoken to. The freezes still happen — that's a firmware issue I can't fix — but the recovery is automatic and invisible.</p>
<p>The integration is open source and installable via HACS: <a href="https://github.com/azgooon/ha-hk-citation">github.com/azgooon/ha-hk-citation</a>. If you have Citation speakers and a Home Assistant instance, give it a try. The speakers deserve it — they really do sound great when they're actually working.</p>
]]></content:encoded>
            <category><![CDATA[Home Assistant]]></category><category><![CDATA[IoT]]></category>
        </item>
        
        <item>
            <title><![CDATA[Deploying Statamic on Proxmox LXC Containers]]></title>
            <link>https://sobesto.com/blog/deploying-statamic-with-proxmox-lxc</link>
            <guid isPermaLink="true">https://sobesto.com/blog/deploying-statamic-with-proxmox-lxc</guid>
            <pubDate>Tue, 17 Feb 2026 00:00:00 +0000</pubDate>
            <description><![CDATA[Running a Statamic blog inside an LXC container on Proxmox — lightweight, fast, and easy to snapshot. A walkthrough of the setup.]]></description>
            <content:encoded><![CDATA[<h2>Why LXC?</h2>
<p>Docker gets all the hype, but LXC containers are an underrated option for hosting small sites. They're lighter than VMs, give you a full Linux userspace, and on Proxmox they're dead simple to manage.</p>
<p>For a flat-file CMS like Statamic, an LXC container is perfect: you get full filesystem access, easy snapshots for backups, and none of the Docker networking complexity.</p>
<h2>The Setup</h2>
<p>Here's the high-level architecture:</p>
<ol>
<li><strong>Proxmox host</strong> runs on a dedicated machine</li>
<li><strong>LXC container</strong> (Debian) hosts PHP, Nginx, and the Statamic app</li>
<li><strong>Cloudflare Tunnel</strong> handles HTTPS and DNS</li>
<li><strong>Git deploy</strong> pushes content and code changes</li>
</ol>
<h2>Container Configuration</h2>
<p>I'm using a minimal Debian LXC with:</p>
<ul>
<li>PHP 8.2 with common extensions (mbstring, xml, curl, zip)</li>
<li>Nginx as the web server</li>
<li>Composer for PHP dependency management</li>
<li>Node.js for building frontend assets</li>
</ul>
<p>The container uses about 256MB of RAM at idle — much lighter than a Docker setup with separate containers for each service.</p>
<h2>Deployment Flow</h2>
<p>The deployment is simple:</p>
<pre><code class="language-bash"># SSH into Proxmox, then into the container
ssh root@proxmox &quot;pct exec 103 -- bash -c '
    cd /var/www/sobesto-blog
    &amp;&amp; git pull
    &amp;&amp; composer install --no-dev
    &amp;&amp; npm run build
    &amp;&amp; php artisan cache:clear
    &amp;&amp; php please stache:clear
'&quot;
</code></pre>
<p>Since Statamic is flat-file, there are no database migrations to worry about. Pull the code, build assets, clear cache — done.</p>
<h2>Backups</h2>
<p>Proxmox makes backups trivial. I snapshot the entire container weekly, and since all content is in git, I have a second layer of backup automatically.</p>
<h2>Trade-offs</h2>
<p>The main downside vs. a managed platform is that you're responsible for security updates and server maintenance. But for a personal blog, the control and simplicity are worth it.</p>
<p>LXC containers are one of those tools that, once you use them, you wonder why you ever reached for anything heavier.</p>
]]></content:encoded>
            <category><![CDATA[DevOps]]></category><category><![CDATA[Statamic]]></category><category><![CDATA[Laravel]]></category>
        </item>
        
    </channel>
</rss>
