<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en"><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://462eng.github.io/feed/daily.xml" rel="self" type="application/atom+xml" /><link href="https://462eng.github.io/" rel="alternate" type="text/html" hreflang="en" /><updated>2026-10-19T17:23:26+00:00</updated><id>https://462eng.github.io/feed/daily.xml</id><title type="html">462eng notes | Daily</title><subtitle>Technical notes from 462engineering: Linux, graphics, VR and the odd kernel/compositor bug we ran into while building things.</subtitle><author><name>Jonathan (462engineering)</name></author><entry><title type="html">2026-10-19</title><link href="https://462eng.github.io/2026/10/19/2026-10-19/" rel="alternate" type="text/html" title="2026-10-19" /><published>2026-10-19T13:20:00+00:00</published><updated>2026-10-19T13:20:00+00:00</updated><id>https://462eng.github.io/2026/10/19/2026-10-19</id><content type="html" xml:base="https://462eng.github.io/2026/10/19/2026-10-19/"><![CDATA[<p>This post covers work from the past week: a camera firmware written
from 14 to 16 October, and a PLC migration done on 12 October. Details
are in the linked posts.</p>

<h2 id="tuyabegone-free-firmware-for-a-cheap-lsc-camera">TuyaBeGone: free firmware for a cheap LSC camera</h2>

<p>A cloud-only indoor camera now runs a small Buildroot system. Video and
audio go through the kernel drivers only: we traced the vendor’s closed
libraries with an <code class="language-plaintext highlighter-rouge">LD_PRELOAD</code> shim and rewrote what they do. It streams
over RTSP and ONVIF, with motion events from the ISP’s own statistics.
Audio switched to AAC because UniFi Protect dropped G.711. It installs
from an SD card in two stages, keeping the factory sensor calibration,
and its first setup requires a press on the physical button.
→ <a href="/2026/10/19/tuyabegone-free-firmware-lsc-camera/">TuyaBeGone: free firmware for a cheap LSC camera</a></p>

<h2 id="a-twincat-3-machine-moves-from-windows-to-tcbsd">A TwinCAT 3 machine moves from Windows to TC/BSD</h2>

<p>A large-format 3D printer moved from the Windows TwinCAT runtime to
TwinCAT/BSD on the same industrial PC. The traps: an engineering
version that did not know the target, a corrupted drive startup
command the older version had ignored, a drive moved to the wrong sync
unit, Windows-only calls in the PLC code, and persistent positions left
behind.
→ <a href="/2026/10/19/twincat-windows-to-tcbsd/">Moving a TwinCAT 3 machine from Windows to TC/BSD</a></p>

<h2 id="how-it-connects">How it connects</h2>

<ol>
  <li>Both are about <strong>leaving a vendor’s platform</strong> (a cloud app, a
Windows runtime) while keeping the hardware.</li>
  <li>In both, the hardest part was the <strong>data that lives on the device</strong>:
the camera’s sensor calibration, the machine’s saved positions.</li>
  <li>In both, the old software <strong>quietly worked around something</strong> that
the replacement had to discover: the vendor library read audio one
period at a time (larger reads block forever), and the old TwinCAT
version skipped a corrupted startup command.</li>
</ol>

<p class="coauthors"><em>Written by Jonathan (462engineering) and Claude (Claude Opus 5.5, Anthropic) — research, debugging and drafting done together in a Claude Code session.</em></p>]]></content><author><name>Jonathan (462engineering) &amp; Claude (Anthropic)</name></author><category term="daily" /><category term="daily" /><category term="camera" /><category term="embedded-linux" /><category term="onvif" /><category term="twincat" /><category term="tcbsd" /><category term="plc" /><summary type="html"><![CDATA[Daily summary: a free firmware for a cheap LSC camera (vendor libraries traced and rewritten, RTSP/ONVIF with motion events, AAC audio for UniFi Protect, two-stage SD install, button-gated setup), and field notes from moving a TwinCAT 3 machine from Windows to TwinCAT/BSD.]]></summary></entry><entry><title type="html">2026-10-12</title><link href="https://462eng.github.io/2026/10/12/2026-10-12/" rel="alternate" type="text/html" title="2026-10-12" /><published>2026-10-12T21:00:00+00:00</published><updated>2026-10-12T21:00:00+00:00</updated><id>https://462eng.github.io/2026/10/12/2026-10-12</id><content type="html" xml:base="https://462eng.github.io/2026/10/12/2026-10-12/"><![CDATA[<p>The day started on the network map and ended in Home Assistant, with a
power cut in the middle that explained two surprises. Details are in
the linked posts.</p>

<h2 id="network-map-wi-fi-zigbee-moved-cables">Network map: Wi-Fi, Zigbee, moved cables</h2>

<p>Our inventory tool now draws Wi-Fi and Zigbee next to the cables, and it
recognises a machine replugged on another switch port (one alert and a
one-click fix instead of three alerts). The Wi-Fi collector has to deal
with an old access point that ejects your admin session. The Zigbee
collector talks to Home Assistant’s websocket from plain PHP. After the
power cut, a storage-network switch came back with its old address:
its settings had never been saved.
→ <a href="/2026/10/12/network-map-wifi-zigbee-moved-cables/">Network map: Wi-Fi, Zigbee, moved cables</a></p>

<h2 id="the-ac-stayed-off-and-a-loop-with-no-hidden-state">The AC stayed off, and a loop with no hidden state</h2>

<p>The air conditioner, used here as a fan, had stayed off since the
afternoon. No automation had done it: a power cut had restarted Home
Assistant, and the smart plug’s power-on state was set to <code class="language-plaintext highlighter-rouge">Off</code>. We then
made it start on high CO2 or VOC. The first version kept a flag
remembering who had switched it on. It was replaced by helpers
recomputed every minute (one per pollutant, each with its own
hysteresis), which the plug follows.
→ <a href="/2026/10/12/stateless-air-circulation-home-assistant/">A stateless air-circulation loop in Home Assistant</a></p>

<h2 id="heating-load-from-a-power-meter">Heating load from a power meter</h2>

<p>Total power is also the heat produced indoors. From it we got the
convector’s real power (980 W), the room’s heat loss (about 105 W/K)
and its base load (about 620 W). This gives a heating-load percentage,
measured and forecast from the weather service, relative to the
convectors that are actually online (one is broken). The dashboard
icons turn green, yellow, red or purple depending on whether the
setpoint can be held. There is also a card-mod 4.2 gotcha with glance
cards.
→ <a href="/2026/10/12/heating-load-from-a-power-meter/">Heating load from a power meter, with a forecast</a></p>

<h2 id="how-it-connects">How it connects</h2>

<ol>
  <li>Airing the room to clear CO2 and VOC means bringing in outdoor air,
and in autumn that <strong>adds heating load</strong>.</li>
  <li>Knowing the heating load is how a future fan, which will also open a
ventilation damper, can decide whether airing the room is cheap or
expensive right now.</li>
  <li>Both use the same pattern: <strong>helpers recomputed from sensors on a
schedule</strong>, and devices that follow them.</li>
  <li>The <strong>power cut</strong> explains two surprises: the storage switch that
forgot its settings, and the AC plug that came back off.</li>
  <li>The <strong>Zigbee view</strong> of the network map showed the plug of the broken
convector offline since late September. The power meter told the
same story: in a week of data, the second convector never ran.</li>
</ol>

<p class="coauthors"><em>Written by Jonathan (462engineering) and Claude (Claude Opus 5.5, Anthropic) — research, debugging and drafting done together in a Claude Code session.</em></p>]]></content><author><name>Jonathan (462engineering) &amp; Claude (Anthropic)</name></author><category term="daily" /><category term="daily" /><category term="inventory" /><category term="network" /><category term="zigbee" /><category term="home-assistant" /><category term="heating" /><category term="air-quality" /><summary type="html"><![CDATA[Daily summary: the network map learns Wi-Fi, Zigbee and moved cables, a switch that forgot its settings after a power cut, a smart plug that stayed off after a power cut, a stateless CO2/VOC air-circulation loop in Home Assistant, and heating load measured from a whole-house power meter, modelled, forecast and coloured on the dashboard.]]></summary></entry><entry><title type="html">2026-09-28</title><link href="https://462eng.github.io/2026/09/28/2026-09-28/" rel="alternate" type="text/html" title="2026-09-28" /><published>2026-09-28T09:00:00+00:00</published><updated>2026-09-28T09:00:00+00:00</updated><id>https://462eng.github.io/2026/09/28/2026-09-28</id><content type="html" xml:base="https://462eng.github.io/2026/09/28/2026-09-28/"><![CDATA[<p>A summary of the day. Each problem below was found while working on the
previous one, so this post follows them in order. Details are in the
linked posts.</p>

<h2 id="bring-up-debian-13-rdna4-valve-index">Bring-up: Debian 13, RDNA4, Valve Index</h2>

<p>The GPU box is a Debian 13 machine with a Radeon AI PRO R9700 (RDNA4).
RDNA4 needs the kernel, Mesa and firmware from trixie-backports. evdi
needs version 1.15.1 to build on kernel 7.1. We also hit a few
boot-time problems (a missing display manager unit, an initramfs that
picked up <code class="language-plaintext highlighter-rouge">.link</code> backups) and a few Steam and Index details.
→ <a href="/2026/09/28/notes-debian-13-rdna4-index/">Small notes: Debian 13 + RDNA4 + Index</a></p>

<h2 id="playing-from-the-desk-sremfb">Playing from the desk: sRemFB</h2>

<p>To use the GPU box from the workstation, sRemFB creates an evdi virtual
display on it and streams uncompressed or LZ4 damage rectangles to a new
windowed viewer. Keyboard, mouse and gamepad go back over uinput, and
audio over UDP. Key to pixel: 8.7 ms median on 2.5 GbE.
→ <a href="/2026/09/28/sremfb-playing-on-the-gpu-box-without-a-codec/">Playing on the GPU box from the desk, without a video codec</a></p>

<h2 id="steamvr-judder-and-moving-to-monado">SteamVR judder, and moving to Monado</h2>

<p>In SteamVR, menus and overlays judder. On Linux, SteamVR runs without
async reprojection. Enabling it, or asking for 120 Hz, ends with
<code class="language-plaintext highlighter-rouge">flip_done timed out</code> and a killed compositor. Monado with SteamVR’s own
Lighthouse driver, xrizer and WayVR runs smoothly at 90, 120 and 144 Hz
on the same hardware.
→ <a href="/2026/09/28/steamvr-linux-overlay-judder-rdna4-monado/">SteamVR on Linux: overlay/UI judder on RDNA4, and why Monado fixes it</a></p>

<h2 id="mutter-never-forgets-an-unplugged-gpu">mutter never forgets an unplugged GPU</h2>

<p>mutter never removes a GPU that has been unplugged, and Xwayland keeps
its DRM fd. Every removed evdi card leaves a stale DRM lease device. In
the end, SteamVR and Monado both stall on them.
→ <a href="/2026/09/28/mutter-never-forgets-unplugged-gpu/">mutter never forgets an unplugged GPU (and what it does to VR)</a></p>

<h2 id="how-the-problems-connect">How the problems connect</h2>

<ol>
  <li><strong>sRemFB</strong> creates <strong>evdi cards</strong> on the GPU box.</li>
  <li>With evdi cards present, <strong>SteamVR’s DRM lease breaks</strong>
(<code class="language-plaintext highlighter-rouge">dc_stream_state is NULL</code>, <code class="language-plaintext highlighter-rouge">flip_done timed out</code>).</li>
  <li>The first fix was to <strong>remove the evdi cards</strong> before starting VR.</li>
  <li>But <strong>mutter never forgets a removed GPU</strong>. Each removed card leaves a
stale <code class="language-plaintext highlighter-rouge">wp_drm_lease_device_v1</code> global, and Xwayland keeps its fd.</li>
  <li>The <strong>stale globals block VR</strong>: SteamVR loops after
<code class="language-plaintext highlighter-rouge">LeaseDevice_Done</code>, and Monado waits forever for <code class="language-plaintext highlighter-rouge">done</code> events, or
picks a stale device that advertises the Index’s connector.</li>
  <li>With a <strong>patched Monado</strong> (bounded wait, no silent fallback, optional
DRM node pinning), Monado ignores the stale devices. It also runs
fine <strong>alongside evdi</strong>, which SteamVR does not.</li>
  <li>So there is no longer any reason to remove evdi cards:
<strong><code class="language-plaintext highlighter-rouge">sremfb-server</code> now runs permanently</strong>. No cards are removed, so no
stale devices are created.</li>
</ol>

<p>A mutter patch for the root cause exists but <strong>has not been tested</strong>. It
is published as an RFC in the mutter post.</p>

<p class="coauthors"><em>Written by Jonathan (462engineering) and Claude (Claude Opus 5.5, Anthropic) — research, debugging and drafting done together in a Claude Code session.</em></p>]]></content><author><name>Jonathan (462engineering) &amp; Claude (Anthropic)</name></author><category term="daily" /><category term="daily" /><category term="vr" /><category term="mutter" /><category term="sremfb" /><category term="debian" /><summary type="html"><![CDATA[Daily summary: Debian 13 + RDNA4 + Index bring-up, SteamVR judder and the move to Monado, mutter stale GPUs and DRM lease devices, sRemFB from the desk, and how they connect.]]></summary></entry></feed>