The Goal
The Raspberry Pi 4 that ran my HyperHDR ambient-lighting setup
died. With current mini-PC and Pi prices, a used Dell Wyse 3040 thin client (Intel Atom
x5-Z8350, 2 GB RAM, 8 GB eMMC) was the cheaper and frankly nicer replacement: fanless,
x86_64, and plenty for HyperHDR’s ~250 MB working set. This note documents rebuilding the
host on it with DietPi - including the part that the official installer would not do for
me, and getting the 10-bit p010 capture format working on x86 via DKMS.
The Dell Wyse 3040 - fanless, palm-sized, and the box that replaced the Pi 4. Photo by Goovaeh8fot6yugh, CC0, via Wikimedia Commons.
Why dd-over-SSH instead of the installer ISO
DietPi does ship a NativePC-UEFI-x86_64 installer ISO, and that is the “proper” path.
On my Wyse it did not detect the internal eMMC as a target - could be specific to this unit
or my boot media, I did not dig in. The simpler route turned out to be the plain disk image
(.img.xz): a
DietPi image written byte-for-byte onto a disk is a bootable DietPi - whatever storage
you write it to becomes the system. (That was the bit I tripped over at first: I flashed
the image to a USB stick before realising the image modifies exactly the target you point
it at, so the target should be the eMMC, not a stick.)
So the plan: boot a throwaway live OS on the Wyse, and from my workstation stream the DietPi image straight onto the Wyse’s eMMC over SSH.
Steps
1. Enable USB boot in the Wyse BIOS
Press F2 at power-on, enter the default BIOS password Fireport, then System
Configuration → USB Configuration and turn USB boot on. Details for this specific box
are in Dell’s KB article;
I won’t repeat them here.
2. Boot a live OS with SSH
Boot any live system that gives you SSH and dd - I used an Arch ISO out of habit, but
anything works. On the live console, set a root password so you can log in over the
network:
passwd
3. Download the DietPi image
On your workstation, grab the NativePC-UEFI-x86_64 disk image (the .img.xz, not the
installer ISO) from the DietPi images index. No
need to unpack it - the next step decompresses it on the fly, so the 1+ GB image never
lands on disk.
4. Flash the image onto the eMMC over SSH
Decompress the image and stream it from your workstation into dd running on the Wyse,
writing to the internal eMMC (/dev/mmcblk0):
xzcat DietPi_NativePC-UEFI-x86_64-Trixie.img.xz | pv | \
ssh root@10.10.10.223 dd of=/dev/mmcblk0 bs=4M conv=fsync iflag=fullblock
xzcat unpacks the .img.xz straight into the pipe; pv gives a progress bar; bs=4M
copies in 4 MiB blocks (far fewer, larger writes than the default 512 B); conv=fsync and
iflag=fullblock make the write reliable. (I use an insecssh alias - plain ssh with
host-key checking disabled - because the live system is ephemeral and its host key changes
every boot.)
5. Edit dietpi.txt before the first boot
DietPi applies dietpi.txt once, on the first boot. Edit it now, while the eMMC is still
mounted on the live system, so the first boot comes up already configured. Mount the
DietPi root partition and edit the file:
mount /dev/mmcblk0p3 /mnt
# edit /mnt/dietpi.txt
Rather than dump the whole file, here are the meaningful deviations from the stock
dietpi.txt, grouped by intent.
Unattended, headless first boot - this is what lets the box come up over the network with no monitor:
AUTO_SETUP_AUTOMATED=1
AUTO_SETUP_GLOBAL_PASSWORD=...
AUTO_SETUP_SSH_PUBKEY=ssh-rsa AAAA...
SOFTWARE_DISABLE_SSH_PASSWORD_LOGINS=1
AUTO_SETUP_NET_HOSTNAME=hyperhdr
The build toolchain for the P010 DKMS module, plus a few diagnostics, installed while the system is still online and writable:
AUTO_SETUP_APT_INSTALLS=mmc-utils v4l-utils usbutils dkms build-essential linux-headers-amd64
dkms build-essential linux-headers-amd64 are what step 8’s dkms-installer.sh needs to
compile the p010 kernel module; v4l-utils (v4l2-ctl) and usbutils (lsusb) help
diagnose the grabber; mmc-utils reports eMMC health.
eMMC-friendly choices - 2 GB of RAM against a ~250 MB workload means swap is pure write wear for no benefit:
AUTO_SETUP_SWAPFILE_SIZE=0
Regional and misc cleanups:
AUTO_SETUP_LOCALE=en_US.UTF-8
AUTO_SETUP_KEYBOARD_LAYOUT=us
AUTO_SETUP_TIMEZONE=Europe/Kyiv
CONFIG_SERIAL_CONSOLE_ENABLE=0 # SSH-managed; does NOT touch the RP2040's /dev/ttyACM*
CONFIG_ENABLE_IPV6=0
SURVEY_OPTED_IN=0
CONFIG_GPU_DRIVER=none # x86 Intel; HyperHDR's USB grabber needs no GPU driver
I left AUTO_SETUP_NET_USESTATIC=0 and instead pinned the DHCP lease to a fixed address on
the router, so the web UI stays reachable at a stable address without hard-coding it here.
6. Boot DietPi from the eMMC
Flush, unmount, reboot, and pull the USB stick so the Wyse boots from the eMMC:
sync && umount /mnt && reboot
7. Let the unattended install finish
With AUTO_SETUP_AUTOMATED=1 the first boot runs the whole DietPi setup without prompts.
It reboots itself a couple of times along the way; let it settle.
8. Install HyperHDR, the P010 module, and ezcoo-usb-control
HyperHDR (by awawa-dev) is not in the DietPi
catalogue, so install its .deb by hand. On x86 there is no pre-patched image, so the
p010 V4L2 support is built as a DKMS module from awawa-dev’s
P010_for_V4L2 instead (the build toolchain
came in via AUTO_SETUP_APT_INSTALLS in step 5). The third package,
ezcoo-usb-control, is my own
small service that bridges the EZCOO HDMI matrix’s USB-serial control protocol to Home
Assistant over MQTT - it is what lets HA switch matrix inputs and read state.
cd /tmp
wget https://github.com/awawa-dev/HyperHDR/releases/download/v22.0.0.0beta2/HyperHDR-22.0.0.trixie.beta2-x86_64.deb
dpkg -i HyperHDR-22.0.0.trixie.beta2-x86_64.deb || apt -f install -y # pull missing deps
wget https://raw.githubusercontent.com/awawa-dev/P010_for_V4L2/refs/heads/master/dkms-installer.sh
chmod +x ./dkms-installer.sh
./dkms-installer.sh 2 # 2 = Debian/Ubuntu (DietPi counts here too)
wget https://gitea.berezovskyi.dev/oleksandr/ezcoo-usb-control/releases/download/v1.0.0/ezcoo-usb-control_1.0.0_amd64.deb
dpkg -i ezcoo-usb-control_1.0.0_amd64.deb || apt -f install -y
Letting dpkg -i fail and following with apt -f install is more robust than naming
dependencies (like libglib2.0-0t64) by hand - the names drift between releases, apt
just resolves whatever the .deb actually needs.
9. Point ezcoo-usb-control at the right serial port and MQTT broker
# /etc/ezcoo-usb-control/config.yaml
device:
port: /dev/ttyUSB0
baud: 57600
poll_interval: 5s
mqtt:
broker: tcp://homeassistant.iot.lviv:1883
username: "ezcoo-usb-control"
password: "<your-mqtt-password>" # set to your broker password
client_id: ezcoo-usb-control
base_topic: ezcoo
discovery_prefix: homeassistant
systemctl restart ezcoo-usb-control
The matrix then shows up in Home Assistant through MQTT discovery.
HyperHDR’s own application config - LED layout, the P010 LUT, smoothing, the automations' expectations - I restored from a backup of the old host, so it all came over verbatim rather than being re-entered by hand. The rest of the rig (capture card, LED controller, wiring) is physically unchanged from the main setup note.
What Works
The same job runs on the Wyse as on the old Pi 4 - no problems for me, though this is a
fresh ARM→x86 move on beta software, so your mileage may vary. The key thing to verify is
that the DKMS module actually makes p010 visible to V4L2 on a stock x86 kernel:
# dkms status v4l2-p010
v4l2-p010/1.0, 6.12.90+deb13.1-amd64, x86_64: installed (Original modules exist)
# v4l2-ctl --list-formats-ext -d /dev/video0 | grep -i -A3 p010
[5]: 'P010' (10-bit Y/UV 4:2:0)
Size: Discrete 1920x1080
Interval: Discrete 0.017s (60.000 fps)
Interval: Discrete 0.033s (30.000 fps)
In normal operation (TV on, capturing) HyperHDR keeps one core busy - a single hyperhdr
process around 70% of a core, ~115 MB RES, ~230 MB of system memory used out of 2 GB - with
the Atom’s cores at 48-51 °C, fanless. When the TV is off the LED instance is disabled, so
there is nothing to process and the host drops back to essentially idle.
ezcoo-usb-control reconnects to the matrix on /dev/ttyUSB0 and re-announces over MQTT,
so Home Assistant picks it up without changes.
Caveats / Open Items
The p010 support is an out-of-tree DKMS module, and that is the most likely future
breakage. DKMS rebuilds it automatically on a kernel upgrade only while the matching
linux-headers-amd64 package stays installed. If the headers ever get removed, the next
kernel update silently boots without the module and capture loses P010 - so it is worth
keeping the headers pinned and glancing at dkms status after kernel upgrades.
Disk is the other thing to watch: Trixie plus the DKMS toolchain (build-essential and
kernel headers) is a tight fit on the 8 GB eMMC, so keep an eye on df -h.
Finally, the install is still a manual sequence after first boot. DietPi’s
AUTO_SETUP_CUSTOM_SCRIPT_EXEC could fold steps 8-9 into a script that runs at the end of
the first boot, making the box reproducible from dietpi.txt alone.
I haven’t bothered yet - it’s a one-off rebuild - but it’s the obvious next step if this host ever dies too.