Overview
MagicMirror² is an open-source smart display platform built on Node.js. It puts a screen on the wall that shows the information you care about, such as weather, news and home sensors, in a clean, always-on layout.
This dashboard is tuned for Queenstown, NZ. It shows:
- Clock, date and a 5-day weather forecast
- Live Home Assistant states for doors, the front door lock, lights, motion, the fridge door and garden irrigation
- Spotify now playing
- An NZ and world news ticker
- ASX lithium stocks and oil prices
- NASA’s Astronomy Picture of the Day as a darkened background
- Two live local webcams, one at the airport and one NZTA traffic camera
The first version of this post covered an Odroid-N2 build. The dashboard has since moved to a Gigabyte Brix mini PC, and this revision describes that build. The Odroid notes are kept where they are still useful. Internal hostnames and addresses are left out, and the diagram uses generic role names.
Hardware: Third Time Lucky
Pi Zero W: a dead end
The first attempt used a Pi Zero W. Its ARMv6 CPU is the problem. Current Chromium, Firefox and Electron builds need ARMv7 with NEON at minimum, so a browser-based dashboard cannot run on it at all.
Odroid-N2: worked, but fiddly
The Odroid-N2 was in the parts box. It is an arm64 board with 4GB of RAM and eMMC storage, and it ran everything. Getting there involved several quirks:
- The vendor image site sits behind bot protection that blocks command-line downloads, so images had to be fetched on a PC and copied across.
- A leftover Amlogic/CoreELEC bootloader on the eMMC stopped new images from booting. Zeroing the first few megabytes of the eMMC from an SD-card boot cleared it.
- Snap packages fail silently on the vendor kernel.
- The N2 has no Wi-Fi, so it needed a USB dongle.
None of these was a blocker, but each one cost an evening.
Gigabyte Brix: boring, which is the point
The current host is a Gigabyte Brix (Celeron 2955U) with 16GB of RAM, an mSATA SSD and wired gigabit Ethernet. It runs Ubuntu Server 24.04 LTS. It is old and modest, but it is x86. Every package is a standard Ubuntu package, and the Intel integrated graphics gives working hardware acceleration in the browser through the stock i915 driver.
| Pi Zero W | Odroid-N2 | Brix | |
|---|---|---|---|
| Architecture | ARMv6 | arm64 | x86-64 |
| Modern browser | No | Yes | Yes |
| RAM | 512MB | 4GB | 16GB |
| Storage | SD card | eMMC | mSATA SSD |
| Network | Wi-Fi | USB Wi-Fi dongle | Wired gigabit |
| GPU acceleration | n/a | Vendor-dependent | Mainline i915 |
The Brix is far more machine than a dashboard needs. In exchange, there are no special images, no vendor kernels and no architecture surprises.
Architecture
(reference copy)"] Display["Display host
X11 + Openbox"] FF["Firefox kiosk"] MM["MagicMirror²"] Cam["Local webcam
file server"] Cron["cron fetch job"] HA["Home Assistant"] APIs["Cloud APIs
weather / NASA / Spotify / finance / RSS"] Webcams["Public webcams"] Dev -- "scp" --> Display Display --> FF --> MM MM -- "WebSocket" --> HA MM --> APIs MM -- "iframes" --> Cam Cron -- "fetch images" --> Webcams Cron --> Cam
A reference copy of the configuration lives on a development container and is copied to the display with SCP. If the display box dies, the config, CSS, webcam pages and autostart scripts can be pushed to a fresh install. The one rule is discipline: anything changed on the display must be copied back straight away, so the two copies do not drift apart.
Kiosk Mode
The Odroid build auto-logged into a TTY and ran startx. The Brix uses a slightly more structured stack. X11 runs Openbox, a minimal window manager, and the Openbox autostart launches Firefox in kiosk mode pointed at MagicMirror.
Lessons carried over from the Odroid build, plus some new ones:
- Firefox comes from the Mozilla PPA, not the snap. The snap failed silently on the Odroid’s kernel, and it also misbehaves in kiosk mode on stock Ubuntu. The PPA is pinned at a high priority before installing, so apt never pulls the snap back in as a dependency.
- Firefox re-enables screen blanking. The
xset s off,xset -dpmsandxset s noblankcommands have to run after Firefox starts. Running them only at session start is not enough. - First boot can need
--no-sandbox --no-first-runto get past Firefox’s first-run and sandbox behaviour in a bare X session. - Display on/off scripts find the X auth file dynamically. They look up the current
serverauth.*file instead of hard-coding a path, so they keep working across reboots. - Ubuntu minimal is really minimal.
cronandpingwere not installed. The webcams depend on cron, and its absence only showed up as frozen images.
Screen Layout
| Zone | Module |
|---|---|
top_bar | Stock ticker (ASX lithium + oil) |
top_left | Clock and date |
top_center | Airport webcam |
top_right | Current weather + 5-day forecast |
bottom_left | Spotify now playing |
bottom_center | NZTA traffic camera |
bottom_right | Home Assistant panel |
bottom_bar | News ticker |
fullscreen_below | NASA APOD background |
Modules
Clock and weather. These are the built-in modules. The clock uses 24-hour time in the Pacific/Auckland timezone. Weather comes from OpenWeatherMap and is shown twice: current conditions, and a 5-day forecast with sunrise and sunset times.
Home Assistant. MMM-HomeAssistantDisplay keeps a WebSocket open to Home Assistant, so state changes appear within a second or two instead of on a polling interval. Each entity uses a Jinja2 template to render an MDI icon with a colour that depends on state, for example a red door icon when the door is open. Home Assistant is addressed by hostname rather than IP so the TLS certificate matches.
Spotify. The live module is MMM-NowPlayingOnSpotify. It replaced an earlier Spotify module, which is now retired. It needs a Spotify developer app and a one-time authorisation to get a refresh token. After that it runs unattended. One trap for headless setups: since early 2025, Spotify only accepts HTTPS redirect URIs, with an exception for loopback http://127.0.0.1. Plan for that when re-authorising from a box with no desktop browser.
News. The original build used a third-party ticker module whose repository has since disappeared. The built-in newsfeed module replaced it, scrolling RNZ, Stuff NZ and BBC World. 1News (broken RSS), Reuters (CORS) and NZ Herald (RSS discontinued) were tried and dropped.
Stocks. MMM-Jast pulls ASX lithium stocks plus WTI and Brent crude from Yahoo Finance. It needs no API key.
Background. MMM-Wallpaper rotates through recent NASA APOD images with a crossfade. A 30% brightness filter keeps the foreground readable.
Webcams Through a Local Proxy
The airport webcam cannot be embedded directly. The site sends X-Frame-Options: SAMEORIGIN and rejects image requests without the expected Referer. The fix:
- A cron job fetches the current image every minute and saves it locally, sending the headers the site expects.
- A tiny static file server on the display host serves those images.
- A small HTML wrapper page reloads the image with a cache-busting query string, so Firefox never shows a stale frame.
- MagicMirror’s
MMM-iFramemodule points at the local page.
The NZTA traffic camera used to be embedded directly. It now goes through the same local path. Every webcam behaves the same way, and Firefox caching stops being a per-camera problem.
What Worked / What Didn’t
Worked
- Moving to x86 removed a whole class of problems: no vendor images, no bootloader cleanup, no snap breakage.
- Wired Ethernet has been more reliable than the USB Wi-Fi dongle.
- WebSocket updates from Home Assistant make the panel feel live.
- The local webcam proxy normalised two quite different sources into one pattern.
Didn’t (or needed rework)
- Hardware that technically works can still cost far more time than an old mini PC that just works.
- Third-party MagicMirror modules come and go. The news ticker vanished, and the Spotify module was replaced.
- The airport webcam depends on an undocumented image URL and header check. It could break at any time, and that is the price of scraping.
Lessons Learned
- Check CPU architecture first. ARMv6 cannot run a modern browser, so anything browser-based is out.
- Prefer boring hardware for always-on displays. A second-hand x86 mini PC beats a clever SBC once your time is counted.
- Avoid snap Firefox for kiosks. Use the Mozilla PPA and pin it.
- Disable blanking after the browser starts, not just at session start.
- Minimal images really are minimal. Check that cron and other basics exist before relying on them.
- Keep a reference copy of the config off the device and treat it as the source of truth.
- Proxy awkward embeds locally. A fetch-and-serve loop with cache-busting is simple and robust.
- Expect third-party modules to disappear. Built-in modules are worth a small loss of polish.
Comments