A second Raspberry Pi 5 and a 10″ touch display, mounted on the wall — not a mirror you glance at, but a surface you touch. The idea: MagicMirror as the ambient “screensaver,” and a single swipe to pull up a live view of whether the house’s systems are actually up. The heavy lift was never the software — it was getting a rotated touch panel to behave like a finished appliance instead of a Pi with a browser open.
Two boxes, one job each: the existing Arcanum host serves MagicMirror; the new panel is a pure display client that renders it and adds its own status view on top. No MagicMirror install on the panel at all — nothing competing for a 1GB Pi.
Step 1 — The panel that wouldn’t light upFresh flash, DSI ribbon seated, GPIO power connected — and a dead black screen. The instinct is to reseat cables and swap ports (I did, all of it). The actual fix was the least-invasive one I should have tried first: apt full-upgrade + rpi-eeprom-update. The Touch Display 2 is newer than a lot of OS images, and its backlight only turns on after the Pi initializes it over DSI — so “no backlight” wasn’t a dead panel, it was stale firmware that didn’t know how to talk to it. Lesson filed: on new hardware, update firmware/kernel before touching a single cable.
sudo apt update && sudo apt full-upgrade -y
sudo rpi-eeprom-update -a
sudo reboot
Step 2 — Landscape, and the double-rotation trap
The panel is natively portrait (1200×1920); the wall mount is landscape. The trap: rotating in two places at once. A video=…,rotate=90 on the kernel cmdline rotates the console, but X’s modesetting driver ignores it and comes up native — so adding an xrandr rotate on top stacks a second rotation and squishes everything into a portrait sliver. The clean answer is exactly one source of rotation: drop the cmdline rotate, let X own it via a server-level config so the screen is landscape from the first frame.
# /usr/share/X11/xorg.conf.d/90-dsi-rotate.conf
Section "Monitor"
Identifier "DSI-1"
Option "Rotate" "left"
EndSection
Step 3 — Touch that follows the picture
Rotating the display doesn’t rotate the touch — so taps landed 90° out of phase (touch bottom-left, dot appears bottom-right). Two gotchas here: the tool xinput wasn’t even installed (so the mapping had been silently failing), and the touch device isn’t named “touch” — it’s ili_v3, so every grep touch came up empty. The fix is a coordinate-transformation matrix that composes the compensating 90° rotation:
xinput set-prop "ili_v3" "Coordinate Transformation Matrix" 0 -1 1 1 0 0 0 0 1
A clean diagonal swipe in libinput debug-events confirmed the digitizer itself was linear and healthy — it was purely a mapping problem, and the matrix put finger and pixel back together at all four corners.
Browser JavaScript can’t ping hosts directly, so the panel needs a tiny local backend. A ~40-line Python service (standard library only — no framework to break on a 1GB box) runs concurrent TCP socket checks against the three things worth knowing about — the internet (Cloudflare DNS), the NAS, and the Arcanum host — and serves the result as JSON. An Arcanum-styled page polls it every 10 seconds and flips each tile teal for up or red for down. A systemd unit keeps it alive across reboots.
# TCP reachability — no ping, no subprocess, no PATH gremlins
def check(host, port):
try:
with socket.create_connection((host, port), timeout=2):
return True
except OSError:
return False
Step 5 — One screen, two views (the swipe)
MagicMirror guards itself with X-Frame-Options: SAMEORIGIN, so it refuses to load in an iframe from any other origin. The fix is a small nginx reverse proxy on the panel that proxies MagicMirror and strips that one header (keeping the WebSocket upgrade so live modules keep ticking). Then a wrapper page stacks two full-screen layers — the mirror and the status view — with transparent gesture strips at the top and bottom edges: swipe up for controls, swipe down (or 30 seconds idle) back to the mirror.
# nginx: strip the frame guard, keep sockets alive
location / {
proxy_pass http://arcanum-host:8080;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_hide_header X-Frame-Options;
proxy_read_timeout 120s;
}
Step 6 — Making it an appliance
Console autologin, then a guarded startx that only fires on the physical console (never on SSH), launching Chromium in kiosk mode at the wrapper. The last detail is the one that separates “finished” from “half-baked”: a stray mouse pointer on a touch screen. Killed at the source — not hidden after idle, but never drawn at all:
exec startx -- -nocursor
Power on → autologin → kiosk → MagicMirror, with the status view a swipe away. No keyboard, no pointer, survives a power cycle — and it’s on the UPS.
Dependencies: Raspberry Pi 5 (1GB) + Raspberry Pi Touch Display 2 (10″); Raspberry Pi OS Lite + a minimal X/Chromium kiosk (xserver-xorg, xinit, chromium, xinput, xserver-xorg-legacy, fonts-noto-color-emoji); nginx (reverse proxy + header strip); a standard-library Python status service under systemd. Server lives on the Arcanum host; this panel is the client. Least-invasive first — update firmware before you ever reach for a screwdriver. Still ahead: the printed enclosure with a motion-sensor cutout, and the “IYKYK” control tiles (EV charging, the smart lock).