Skip to content

Instantly share code, notes, and snippets.

@raunakdoesdev
Created July 14, 2026 15:59
Show Gist options
  • Select an option

  • Save raunakdoesdev/74f06b759d412ca33e66b78af8ee8ed1 to your computer and use it in GitHub Desktop.

Select an option

Save raunakdoesdev/74f06b759d412ca33e66b78af8ee8ed1 to your computer and use it in GitHub Desktop.
My Home Assistant setup: Mac mini + UTM, Zigbee/Z-Wave, devices, automations, iPhone alarm sync, and config-as-code

My Home Assistant setup: a Mac mini, two radios, and automations that behave like household software

Last updated July 2026. This write-up was assembled with help from OpenAI Codex from my live Home Assistant device registry and configuration, then reviewed for public sharing. Names, addresses, entity IDs, network details, tokens, and private endpoints are intentionally omitted.

I run Home Assistant because I want the house to respond to context, not just expose a larger collection of remote controls. The interesting part is less "I can turn a bulb on from my phone" and more "the house understands who is home, which alarm matters, whether I just arrived in the Tesla, and whether opening the shower door means entering or exiting."

This is the current architecture, device inventory, automation layer, and deployment workflow.

Runtime architecture

The always-on host is a Mac mini. Home Assistant OS runs inside a UTM virtual machine, bridged onto the LAN so it behaves like a normal appliance on the network.

Two USB radios are passed through from macOS into the VM:

  • A Sonoff ZBDongle-E running ZHA for Zigbee.
  • A Sonoff Z-Wave dongle running Z-Wave JS.

Both radios use stable /dev/serial/by-id/... paths inside Home Assistant rather than volatile ttyUSB0/ttyUSB1 names. USB auto-connect is enabled in UTM. This matters: after moving a dongle to an extension cable, the Mac can still see it while the VM no longer owns it. Stable paths plus explicit passthrough make that failure much easier to diagnose.

The split looks roughly like this:

Mac mini
└── UTM virtual machine
    └── Home Assistant OS
        ├── Home Assistant Core
        ├── ZHA ───────── Sonoff Zigbee coordinator
        ├── Z-Wave JS ─── Sonoff Z-Wave coordinator
        ├── Music Assistant
        ├── Terminal/SSH add-on
        └── Matter Server

The VM gets backed up through Home Assistant's automatic backups. Configuration is additionally managed as code from a private Git repository.

Connected hardware and services

Zigbee

  • Two Third Reality color bulbs, used as the bedroom's paper lamp and large mushroom lamp.
  • Tuya TS0301 shade motor, exposed as the bedroom shade.
  • Aqara/LUMI contact sensors on the shower door and the door from the garage.
  • Aqara MotionAQ2 motion/occupancy sensor watching the closet.
  • Aqara wireless button remote for direct bedroom controls.
  • An additional battery button on the other side of the bed.

Z-Wave

  • Honeywell TH6320ZW2007 thermostat, controlling heat only. It reports room temperature, humidity, operating state, mains/battery status, and accepts heating setpoints.

Local Wi-Fi, Matter, and media

  • TP-Link/Kasa HS200 switches for bathroom, sink, shower, and closet lighting.
  • TP-Link EP40 outdoor plug for backyard string lights.
  • Sonos Era 100 in the bedroom.
  • Google Nest Hub in the entryway.
  • Amazon Echo Show in the kitchen.
  • Sonoff Matter air-quality device, reporting temperature, humidity, PM2.5, and PM10.
  • Levoit Core 300S air purifier.

Larger integrations

  • Tesla Fleet for a Model Y: location, plug state, charging state, battery, charge controls, climate, locks, windows, and arrival estimates.
  • Eight Sleep Pod 4, with separate bed-presence, sleep, temperature, and climate entities for each side.
  • Whisker Litter-Robot 4, including waste drawer and litter levels.
  • Roborock Q7 Max, including maps, room state, dock state, and cleaning controls.
  • Bambu Lab P1S, including camera, print status, temperatures, fans, progress, and controls.
  • Home Assistant Companion App presence for both household members.
  • Adaptive Lighting through HACS.
  • Music Assistant for media routing and playback.

The automation layer

There are currently about twenty YAML-managed automations. These are the ones that make the setup feel cohesive.

A two-person alarm-driven morning

iOS is the source of truth for alarms. Each person's iPhone runs a tiny Personal Automation early in the morning:

  1. Find the next enabled alarm in the desired time window.
  2. If one exists, call that person's Home Assistant sync script with the alarm time.
  3. If none exists, call that person's clear script.

All interpretation lives in Home Assistant. Each person has three helpers:

  • alarm_set: whether an upcoming alarm exists.
  • alarm_time: the next alarm's time.
  • alarm_last_sync: when the phone last reported, including a valid "no alarm" result.

This design keeps Apple Shortcuts intentionally stupid. The phone only transmits one fact; YAML handles shared-household policy.

When both people are home:

  • The earliest active alarm determines when heating starts.
  • The latest active alarm determines shared bedroom actions, so an earlier riser does not wake the later sleeper.
  • Bedroom lamps leave Adaptive Lighting sleep mode ten minutes before the later alarm.
  • The shade rises two minutes before the later alarm.
  • A morning status briefing runs five minutes after the later alarm.

The thermostat target is 74°F for the wake period. I originally started preheating 30 minutes early, but recorder data showed the thermostat moving from 70°F to 74°F in about 12 minutes. The automation now starts 12 minutes before the first alarm, based on the measured response rather than a guess. One hour after the last alarm, it returns to the normal home setpoint.

A 4:15 AM watchdog checks both last_sync helpers. If either iPhone failed to report, Home Assistant creates an explicit alert instead of silently skipping the morning.

Heating outside the wake sequence

The heater also follows occupancy:

  • If everyone leaves for ten minutes, use the energy-saving preset and a 62°F target.
  • When either person returns, clear the energy preset and restore the 70°F home target.
  • The morning 74°F interval temporarily overrides those normal targets.

Presence is sourced from each person's phone. The Tesla is deliberately not attached to the person entity: a parked car should not convince the house that its owner is home.

Bedroom button and lighting

The Aqara button maps physical gestures to predictable actions:

  • Single press: toggle both bedroom lamps.
  • Double press: toggle Adaptive Lighting sleep mode.
  • Long press: toggle the bedroom shade.

Adaptive Lighting handles color temperature and brightness throughout the day. At bedtime it switches to a very dim orange sleep profile; before the shared wake time it fades the lamps on over five minutes.

The lamps turn off after both people leave and return when someone arrives.

Closet occupancy

The Aqara MotionAQ2 is mounted to see activity at the sliding closet rather than trying to infer the door's exact position. Any motion or occupancy while the closet switch is off turns it on. Once both sensor signals clear, Home Assistant waits two minutes, verifies they are still clear, and turns the closet lights off. The automation uses restart mode so new motion cancels the pending shutdown.

Shower-door state machine

A contact sensor on the glass shower door controls a dedicated shower light, but a simple open/close rule is wrong for the actual routine: open the door, start the water, wait 30-90 seconds, enter, shower, then exit.

The automation therefore models occupancy:

First door open
  -> light on
  -> occupied = true
  -> start five-minute minimum-occupancy timer

Door activity during first five minutes
  -> reset timer
  -> never turn the light off

Door opens after minimum occupancy
  -> mark exit pending

Door closes while exit pending
  -> wait 30 seconds
  -> occupied = false
  -> light off

Manual light-off or 45-minute timeout
  -> clear occupancy and all pending state

This avoids the worst failure mode: plunging the shower into darkness shortly after someone gets in.

Tesla charging reminder

Opening the garage-to-house door triggers a reminder only when all of these are true:

  • The Tesla arrived home within the last 45 minutes.
  • The charge cable is disconnected or charging reports disconnected.
  • The reminder has not played in the last 30 minutes.

The entryway Nest Hub then announces a reminder to plug in before getting comfortable.

There is also a solo-arrival routine: if one person arrives while the other is away, Music Assistant starts a favorite Spotify playlist on the bedroom Sonos and announces it at the entryway display.

Other routines

  • Backyard string lights run between sunset and 10 PM only while someone is home.
  • Bathroom lights come on shortly after the weekday wake routine and turn off after a 45-minute safety timeout.
  • Tuesday evening produces a persistent garbage-day reminder for Wednesday pickup.
  • The morning briefing announces Litter-Robot waste-drawer and litter levels on Sonos.

Configuration-as-code workflow

Home Assistant's runtime registries and secrets stay on the instance. The private repository contains only declarative YAML and tooling:

config/
├── configuration.yaml   # helpers, groups, top-level integration config
├── automations.yaml     # behavior
├── scripts.yaml         # reusable actions and iPhone alarm ingestion
└── scenes.yaml

tools/
├── YAML validation
├── entity/reference validation
├── Home Assistant config validation
└── API-based reload tooling

The daily workflow is:

make pull      # rsync config down from the HA VM
make validate  # syntax, references, and HA config checks
make push      # validate, rsync up, reload affected domains through the API

SSH reaches the Terminal add-on inside Home Assistant OS. rsync exclusions protect .storage, databases, logs, backups, tokens, keys, HACS-managed components, and other runtime state from accidental overwrite. Most changes reload automations, scripts, helpers, scenes, and core config without restarting Home Assistant. New helper types or integration configuration still get a Core config check and restart.

Reliability lessons so far

  1. Automate the failure signal. A missed iOS Shortcut used to look exactly like "nothing happened." The per-phone sync timestamp and watchdog make it diagnosable.
  2. Use measured timing. Recorder history turned thermostat preheat from an arbitrary 30 minutes into a measured 12-minute lead.
  3. Model human routines as state machines. The shower is the clearest example; timers plus explicit occupancy state beat clever-looking one-shot triggers.
  4. Keep physical controls. The bedroom button works faster than finding a dashboard and gives the important routines a tactile interface.
  5. Separate configuration from runtime state. YAML belongs in version control; registries, credentials, databases, and backups do not.
  6. Treat USB passthrough as infrastructure. Stable serial paths, VM auto-connect, extension cables, and database backups matter as much as the automations built on top.

The setup is still evolving, but the guiding idea is stable: phones and sensors report facts; Home Assistant owns policy; physical controls remain available; and every automation should fail in a way that is understandable and recoverable.

@n8n-richeotaza

Copy link
Copy Markdown

Small businesses that automate even 2-3 repetitive tasks typically save 10-15 hours per week. The trick is identifying which tasks eat the most time first, then finding the right tool for each one.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment