Building a Self-Hosted Home Automation Platform from Scratch
Raspberry Pi 5 hub, ESP32 HVAC control, audio-reactive Hue lighting, Snapcast multi-room audio, and a native iOS app — all running locally.
By Igor Riera
I’ve spent the last few weeks building a home automation platform that runs entirely on my local network. No cloud subscriptions. No vendor lock-in. A Raspberry Pi 5 in an Argon ONE V3 case with an NVMe SSD, running Home Assistant Supervised, Docker, and a collection of services that control the climate, lights, audio, and network monitoring in my house.
This started as “I want to control my AC from my phone.” It turned into a platform. Here’s what the architecture actually looks like.
The hub
The Raspberry Pi 5 (8GB) sits at 192.168.4.30 on the local network. It runs:
- Home Assistant Supervised: The automation engine. Manages integrations, automations, scenes, and the WebSocket API that the iOS app connects to.
- Docker Compose stack: Jellyfin (media server), Snapcast (multi-room audio), Pi-hole (DNS ad-blocking). Each container runs on a shared bridge network.
- Native systemd services: The audio analyzer for ambient lighting sync, the Snapcast client tap, and an Eero proxy for network monitoring.
Home Assistant Supervised runs outside Docker — it manages its own container stack. This is a deliberate separation. HA’s update cycle is independent from the services I control directly.
Climate control: ESP32 + Mitsubishi CN105
Four Mitsubishi MSZ-GL06NA mini-split units, each controlled by an ESP32-WROOM-32D board connected to the unit’s CN105 diagnostic port. The CN105 port exposes UART at 2400 baud with even parity — a serial protocol that lets you read and write temperature setpoints, fan modes, vane positions, and operating state.
The boards run ESPHome firmware with the cn105 climate component. Each one has a static IP (192.168.4.20 through .23), connects to Home Assistant via the ESPHome API on port 6053, and exposes a local web server on port 80 for diagnostics.
One constraint: the outdoor condenser requires all indoor units to operate in the same mode (heat or cool). I enforce this with an input_select helper in HA — when the system mode changes, an automation pushes the mode to all four units simultaneously.
The ESPHome configs are templated with substitutions — device name, friendly name, and static IP are the only differences between the four units. Everything else is identical.
Lighting: Philips Hue + audio-reactive sync
A Philips Hue Bridge controls 70+ lights across 4 rooms. Basic on/off, brightness, and color control runs through Home Assistant’s Hue integration.
The standout feature is audio-reactive ambient sync — a Python service that reads from the Snapcast audio stream, performs real-time spectral analysis (FFT, beat detection, band energy extraction), and maps the results to Hue light commands via Home Assistant’s API. Four modes: Pulse (beat flash), Cycle (slow color shift), Reactive (full spectral mapping), and Cinema (subtle ambient glow). I wrote a separate blog post on the DSP pipeline if you want the technical details.
The sync service runs as a systemd unit. The iOS app controls the mode via an input_select helper. The analyzer polls the helper every 5 seconds and applies the mode on the next frame.
Multi-room audio: Snapcast
Snapcast handles synchronized audio distribution. Music enters through Music Assistant (which provides a Spotify Connect endpoint called “Cerberus” and manages playback sessions) or AirPlay via shairport-sync. Snapcast distributes the PCM stream to client devices in each room at 48 kHz, 16-bit stereo.
The Snapcast server runs in Docker, exposing port 1704 for streaming, 1705 for control, and 1780 for the JSON-RPC API and web UI. Home Assistant integrates via the Snapcast component for zone grouping and volume control.
Per-zone volume control is available from the iOS app. The Music tab shows each Snapcast group with a volume slider and playback state.
Network monitoring
Three layers of network visibility, all surfaced in the iOS app:
Pi-hole handles DNS for the entire network. The iOS app pulls query counts, blocked queries, and per-client domain breakdowns via Pi-hole’s API on port 80.
ntopng runs on port 3000 and monitors WAN traffic passively. The Pi sits as a transparent Layer 2 bridge between the modem and the Eero mesh gateway — all WAN traffic passes through it. ntopng captures per-device flows, protocol breakdowns, and real-time throughput.
Eero proxy is a small Python service I wrote that bridges the Eero cloud API to a local HTTP endpoint. It exposes per-device bandwidth (live Mbps up/down), signal strength, connection type, and online/offline state. The iOS app’s Network tab aggregates all three sources.
The iOS app
A native SwiftUI app (iOS 17+) controls everything. 53 files, 3,555 lines, zero third-party dependencies.
The networking layer has two clients: a REST client for commands (set temperature, turn on lights, activate scenes) and a WebSocket client for real-time state updates. The WebSocket connects to Home Assistant’s /api/websocket endpoint and subscribes to specific entity IDs. State changes push to the UI via iOS 17’s @Observable macro — no polling, no timers.
Five tabs: Climate (2x2 HVAC dashboard), Lights (room-based controls), Music (Snapcast zones), Scenes (app-activated presets), and Network (Pi-hole + ntopng + Eero). A home screen widget shows current temperature per unit via WidgetKit. Auth tokens live in Keychain.
What I’d tell someone starting
Start with Home Assistant. It’s the single best decision in this stack — it handles integrations, automations, and provides a WebSocket API that makes building custom frontends straightforward.
Use ESPHome for any ESP32 work. Writing raw Arduino sketches for home automation is unnecessary when ESPHome gives you OTA updates, HA integration, and a web dashboard out of the box.
Run everything locally. The moment you depend on a cloud service for a core function, you’ve introduced a failure mode you can’t debug and can’t fix. My thermostat works whether my internet is up or not.
And build the monitoring first. You’ll want to know when a device drops off the network before you notice your house is 85 degrees.