MQTT based presence detection

Updated

I really like to fiddle around with home automation to make my (and my family) life even harder than necessary. I’ve lost count of how many times I’ve had to troubleshoot why my lamps aren’t turning on as expected, or having panic when the lamps do come on in the middle of the night with a baby sleeping near the lamp.

One of my favorite things that never quite work as expected is presence detection. How hard could it be, really? Well, we are two adults (age wise, at least) with multiple devices which all have their own quirks and power saving tweaks that makes most normal methods ineffective.

Does this method solve the problems outlined above? Not at all, but it was easy to implement (at least for my setup) and makes for a short blog post. We’ll be using the MQTT device tracker for this experiment, which in this case is just a fancy DHCP lease tracking.

I’m using Mosquitto as my MQTT client as I’m most familiar with that. It doesn’t really matter which client and/or broker you use. You can probably make most DHCP servers run a hook after a new lease is issued, but that is left as an exercise for the reader. :)

Configuration HASS

For the home assistant configuration, here’s from my device_tracker.yaml (which is just included in the main configuration.yaml):

# device_tracker.yaml
- platform: mqtt
  source_type: router
  devices:
    android_phone: !secret android_phone_topic
    other_device: !secret other_device_topic

The !secret macro (which is awesome and everybody should use it!), and in this case it’s just expanded into a string which contains the topic used for this device. From my secrets.yaml file:

# secrets.yaml
# For some reason my colons are removed?
android_phone_topic: "location/aa:bb:cc:dd:ee:ff"
other_device_topic: "location/gg:hh:ii:jj:kk:ll"

Repeat the above for all relevant devices.

Configuration router

So, what’s sending data into these topics? DNSMASQ on my router, of course! I’ve stolen this idea from this blog post originally but just changed what is sent where.

The general idea is that DNSMASQ sends some data to the script on DHCP lease changes. The only parts we are interested in are MAC addresses and the operation. The latter is either “old”, “add” or “del” which we use with some clever near-AI computations (the if/elif statement) to seamlessly integrate with Home Assistant. Ugh.

I’m also using the MQTT retain feature to remember each lease, so in case Home Assistant is restarted and forgets the leases states, it will be reminded again. This is wasteful and will probalby annoy someone somewhere, but if you have so many devices passing through your network that this will be a problem, then this tutorial probably isn’t for you anyways. And if it is, get hardware from this turn of the century.

#!/bin/sh

op="${1:-op}"
mac="${2:-mac}"
ip="${3:-ip}"
hostname="${4}"

topic="location/${mac}"
payload="${op}"

presence="not_home"
if [ "${op}" = "old" ]; then
  presence="home"
elif [ "${op}" = "add" ]; then
  presence="home"
fi

mosquitto_pub -h ip-to-your-mqtt-broker -u relevant-username -P oh-so-secret-password -t "${topic}" -m "${presence}" -r

Save the above somewhere (/usr/local/sbin/mqtt-lease.sh for example?) and make it executable. The final part is to make DNSMASQ call this script upon DHCP lease changes. This is pretty easy:

echo "dhcp-script=/usr/local/sbin/mqtt-lease.sh" >> /etc/dnsmasq.conf

Boom, done!

Update 2021

I’ve accepted that my shell scripting is terrible and is unlikely to improve any time soon. My lua is even worse, but this newer script is easier to read and I think I’m less likely to shoot myself in the foot if I ever need to change it.

It’s also a part of my ansible repository, which is why I’ve also included some Jinja2 variables into the mix.

#!/usr/bin/env lua
-- Args:
-- 1: operation (add, old, del)
-- 2: mac address
-- 3: ip address
-- 4: hostname

local topic = "location/" .. arg[2]

local presence = "not_home"
if arg[1] == "old" or arg[1] == "add" then
   presence = "home"
end

os.execute("/usr/bin/mosquitto_pub -h {{ dnsmasq_mqtt_address }} -u {{ dnsmasq_mqtt_username }} -P {{ dnsmasq_mqtt_password }} -t '" .. topic .. "' -m '" .. presence .. "'")

Configuration dnsmasq

My dnsmasq configuration is really simple, and the only reason I’m including this section is to mention how I try to improve the precision of the presence detection used here.

To avoid waiting too long before marking a device as away, I just use a short DHCP lease time. I went with 15 minutes as that seems to be a reasonable tradeoff between delay and avoiding too many DHCP lease updates.

dhcp-range=192.168.10.100,192.168.10.200,15m

For devices that are not interesting to track, I just use a ‘static’ lease and increase their lease times.

dhcp-host=xx:xx:xx:xx:xx:xx,dochangemeplease,192.168.10.5,12h