Back to blog

Reliable Presence Detection with iPhones and Node-RED

| 7 min read

Every smart home needs to know one basic thing: is anyone home? Door locks, lights, heating, alarms. They all depend on whether the house is occupied or empty. Getting this right with iPhones is trickier than you’d expect.

I’ve been running a presence detection system in Node-RED and Home Assistant for a while now. It tracks who’s home, counts how many people are in the house, and triggers automations based on that. Here’s how it works and why I built it the way I did.

The iPhone Problem

Home Assistant tracks devices using a device_tracker entity. For phones, this usually means checking if the device is connected to your Wi-Fi network. When the phone is on Wi-Fi, it’s home. When it disconnects, it’s not_home. Simple enough.

Except iPhones don’t play nice with this approach.

When an iPhone’s screen turns off, iOS aggressively manages the Wi-Fi radio to save battery. The phone enters a doze state where it stops responding to network pings and broadcasts. It’s still technically associated with your access point, but your router (or any ping-based tracker) can’t reach it. This is part of the 802.11 power save mechanism, and Apple leans into it hard to preserve battery life.

This means your phone can appear to “leave” your network for several minutes while you’re sitting on the couch. Without a grace period, your smart home would think you left, turn off all the lights, and lock the door. Not great.

The Home Assistant community has dealt with this extensively. The consensus is a 15-minute grace period before marking someone as away. That’s long enough to cover the worst-case iPhone sleep cycle, but short enough that departure detection doesn’t feel sluggish. Home Assistant’s own device_tracker documentation has the consider_home setting for exactly this reason.

My Setup

I’m using UniFi networking gear, and the UniFi integration in Home Assistant provides device tracker entities for every connected client. So each iPhone in the household gets a device_tracker entity that reflects whether it’s connected to the Wi-Fi network.

The UniFi integration is a step up from ping-based tracking because it checks the device’s association state at the controller level. It knows when a device is connected even if it doesn’t respond to pings. But the iPhone sleep issue still applies. The phone can disassociate from the access point entirely during deep sleep, so the 15-minute grace period is still necessary.

If you don’t have UniFi gear, don’t worry. You can get the same result with a simple ping-based device tracker or the nmap tracker. The only difference is that ping and nmap rely on the phone responding to network requests, which makes them slightly less reliable during iPhone sleep cycles. The 15-minute grace period handles that just fine though.

How the System Works

The approach is pretty straightforward. For each person in the household, the system:

  1. Watches their iPhone’s device tracker entity from the UniFi integration
  2. Waits 15 minutes after a not_home state before acting
  3. Toggles an input_boolean to track their presence
  4. Updates a counter that tracks how many people are home

The counter is the key piece. It lets me trigger different automations based on whether someone arrived, someone left, the last person left, or the first person came home.

Here’s the full architecture:

┌──────────────────────────────┐
│ Per-Person Flow │
│ │
│ device_tracker.iphone │
│ (from UniFi integration) │
│ │ │
│ ┌────┴────┐ │
│ │ │ │
│ not_home home │
│ (15 min) │ │
│ │ ▼ │
│ │ Was person away? │
│ │ │ │ │
│ │ Yes No │
│ │ │ (ignore) │
│ ▼ ▼ │
│ Turn Turn │
│ OFF ON │
│ bool bool │
│ │ │ │
│ ▼ ▼ │
│ Counter Counter │
│ -1 +1 │
└──────────────────────────────┘
┌──────────────────────────────┐
│ Counter-Based Events │
│ │
│ counter.personen_thuis │
│ │ │
│ ┌────┼────────┐ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ First Someone Last │
│ person left person │
│ home left │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ (link) (link) Lights off │
│ Lock door │
│ (links) │
└──────────────────────────────┘

The Departure Flow

Each person has a server-state-changed node watching their device tracker. This node has two outputs:

  • Output 1: The phone has been not_home for 15 minutes. This is a confirmed departure. The system turns off their input_boolean (e.g., input_boolean.presence_florian) and decrements the people counter.
  • Output 2: The state changed but the 15-minute condition wasn’t met. This could mean the phone reconnected to Wi-Fi within the grace period.

That second output is where the arrival detection lives.

The Arrival Flow

When the device tracker changes state but doesn’t meet the “not_home for 15 minutes” condition, it means the phone came back online. But I only want to count this as an arrival if the person was actually marked as away.

So the flow checks the input_boolean first. If it’s off (person was marked away), it turns it back on and increments the counter. If it’s already on, the person never left in the first place. It was just the iPhone doing its sleep cycle thing. No action needed.

This is important. Without this check, you’d get phantom arrivals every time an iPhone reconnects after a Wi-Fi nap.

Counting People

The counter.personen_thuis entity tracks how many people are home. Every departure decrements it. Every arrival increments it. A separate flow watches this counter and categorizes the change into four events:

EventConditionExample use
Someone came homeCounter increasedWelcome home notification
Someone leftCounter decreased(Available for future use)
Last person leftCounter hit 0Turn off all lights, lock door
First person homeCounter went from 0 to 1+Turn on hallway light

The “last person left” event is the most useful one. When the counter hits zero, the system turns off every light in the house through a light.alle_lampen group entity and locks the front door. These are the two things I always want to happen when nobody’s home.

The “first person home” and “someone came home” events are available as link nodes in Node-RED, so I can easily hook up new automations to them later without touching the presence flow itself.

Why Input Booleans Instead of Just the Device Tracker

You might wonder why I use input_boolean entities as an intermediate step instead of working directly with the device tracker states. A few reasons:

The grace period creates ambiguity. During those 15 minutes, the device tracker might flip between home and not_home multiple times. The input boolean gives me a clean, debounced state. It’s either on or off, no in-between.

It decouples presence from the detection method. If I ever switch from UniFi to something else (like the Home Assistant Companion App or Bluetooth-based tracking), I only need to change what drives the input boolean. Everything downstream that reads presence state keeps working.

Manual override. Sometimes the system gets it wrong. With an input boolean, I can manually toggle someone’s presence from the Home Assistant dashboard. Can’t do that with a device tracker.

Potential Improvements

This system has been running reliably for a while, but there are a few things that could make it better:

Faster arrival detection. Right now, arrival is detected when the device tracker flips back to home. The Home Assistant Companion App on iOS can detect arrivals faster using GPS and significant location changes. Combining both sources would reduce the delay.

Bluetooth-based tracking. Projects like ESPresense use ESP32 boards to detect Bluetooth advertisements from phones. iPhones broadcast BLE beacons more consistently than they respond to Wi-Fi pings, so this could be more reliable for detecting someone’s presence without the 15-minute grace period.

Guest handling. The current system only tracks known household members. A simple addition would be a guest mode toggle that prevents the “last person left” automations from firing when guests are over.

Getting Started

If you want to build something similar, here’s what you need:

  • Home Assistant with device tracker entities for each phone (I use the UniFi integration, but ping or nmap work too)
  • Node-RED running as a Home Assistant add-on, or just use native Home Assistant automations. I like Node-RED for visual flows, but everything in this post can be done with regular HA automations too.
  • An input_boolean entity per person (create these in Home Assistant under Settings > Devices & Services > Helpers)
  • A counter helper entity for tracking the number of people at home

Start with one person. Get the departure and arrival flow working correctly, then duplicate it for other household members. The counter and event-based automations can be added once the basic tracking is solid.

If you’re into Node-RED and Home Assistant automations, you might also like my post on smart garden watering, which uses a similar approach for weather-based irrigation scheduling.