A big challenge in Home Automation is implementing a reliable mechanism for presence detection - figuring out who's home, and tailoring logic accordingly.
Here's how I do it using Node-Red, Unifi and MQTT.
Many approaches to presence detection rely on interacting with the device itself, either by way of software running on the mobile handset, or having the mobile ping or poll an external service. None of these approaches really appealed to me as they are fiddly, rely on handset configuration & maintenance and need to be reset every time someone gets a new phone.
The approach I've taken here is to rely on the fact that all the families' mobile phones are configured to connect to home WiFi (to save on data charges!). This method interrogates the home network to see what phones are present - a good indication that the person is home.
In short, I have a Node-Red flow that polls the network on a schedule, looks for named devices, and updates presence status in MQTT based on that.
I could have used a database, or flat file storage for storing state, but as I have an MQTT broker running anyway, it's a convenient and flexible approach for me.
UNIFI
The first step is to configure the network to allow all of this to work. I run a Unifi set-up comprising multiple switches, a USG and 4 wireless access points. My Unifi controller lives as a docker on my unRaid server. For this to work, the Unifi controller must be running, but in my case it is 24/7, so box 1 is ticked.
Within Unifi, it's possible to list clients by connection type, so my wireless device list looks like this;
What's important here is that each mobile device to be used for presence detection has a unique name as this string is used later to identify the device.
This can be configured in the device 'general tab in Unifi;
NODE-RED
The first step here is to install the node-red-contrib-unifi node. This provides access to a number of interfaces with the Unifi system, particularly the 'ClientDevices' command, which lists all connected devices.
Here is the main flow in Node-red;
These outputs are structured as topics as they are pushed to MQTT. The rbe node prevents updates if status is unchanged;
While the mqtt node updates the topic in the broker;
Side Note: In writing this post, I realised that my son's recently acquired iWatch is now also showing up, so I'll need to enhance this to take account of that device. But it poses a dilemma - should be be home if both phone and watch are present, or rely on one device only?
UPDATE
As requested in comments, here's an anonymised version of the flows. Note that there's a dependency on having an MQTT broker. This could be re-worked to function entirely in Node-Red, but MQTT allows for other applications and services to access the user states as well;
[{"id":"3d6e9c4a.9cdc04","type":"Unifi","z":"e0922890.07e998","name":"get active devices","ip":"192.168.1.199","port":8443,"site":"default","command":"20","x":870,"y":240,"wires":[["ee630cd6.2aba8"]]},{"id":"b8015ccc.430c9","type":"inject","z":"e0922890.07e998","name":"run every 5 mins","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"300","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":650,"y":240,"wires":[["3d6e9c4a.9cdc04"]]},{"id":"ee630cd6.2aba8","type":"function","z":"e0922890.07e998","name":"check presence","func":"const lastSeenSeconds = 20;\nlet presenceCutoff = (new Date() - (lastSeenSeconds * 1000)) / 1000; \nconst people = {\n \"presence/user1\": \"user1iPhone\",\n \"presence/user2\": \"user2Xperia\",\n \"presence/user3\": \"user3Xperia\",\n \"presence/user4\": \"Duser4iPhone\",\n};\n\n\nreturn Object.keys(people).map(function(topic) {\n //let devices = msg.payload[0].filter(device => device.name === people[topic] && device.last_seen > presenceCutoff);\n let devices = msg.payload[0].filter(device => device.name === people[topic]);\n\n return {\n topic: topic,\n retain: true,\n payload: devices.length > 0\n };\n});\n\n","outputs":4,"noerr":0,"initialize":"","finalize":"","x":1080,"y":240,"wires":[["dbe23e68.45c3","7934121e.fc3aac"],["dbe23e68.45c3","7934121e.fc3aac"],["dbe23e68.45c3","7934121e.fc3aac"],["dbe23e68.45c3","7934121e.fc3aac"]],"outputLabels":["joep presence","monique presence","",""]},{"id":"e0c8176d.7410d8","type":"mqtt out","z":"e0922890.07e998","name":"mqtt: change presence","topic":"","qos":"0","retain":"true","broker":"fee361e0.b1e17","x":1450,"y":240,"wires":[]},{"id":"96c68931.446538","type":"comment","z":"e0922890.07e998","name":"check unifi for presence of phones","info":"","x":680,"y":200,"wires":[]},{"id":"7934121e.fc3aac","type":"rbe","z":"e0922890.07e998","name":"","func":"rbe","gap":"","start":"","inout":"out","property":"payload","x":1250,"y":240,"wires":[["e0c8176d.7410d8"]]},{"id":"dbe23e68.45c3","type":"debug","z":"e0922890.07e998","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","x":1230,"y":140,"wires":[]},{"id":"439195c3.4c403c","type":"ui_text","z":"e0922890.07e998","group":"d86c0487.5aa878","order":0,"width":0,"height":0,"name":"","label":"User1","format":"{{msg.payload}}","layout":"row-left","x":1030,"y":360,"wires":[]},{"id":"56e90f9d.dee72","type":"mqtt in","z":"e0922890.07e998","name":"presence/user1","topic":"presence/user1","qos":"0","datatype":"auto","broker":"fee361e0.b1e17","x":650,"y":360,"wires":[["8527e24b.d15f8"]]},{"id":"25404c61.4c78d4","type":"mqtt in","z":"e0922890.07e998","name":"presence/user2","topic":"presence/user2","qos":"0","datatype":"auto","broker":"fee361e0.b1e17","x":660,"y":420,"wires":[["ad98ccd8.78574"]]},{"id":"f48d4f0e.53dd","type":"mqtt in","z":"e0922890.07e998","name":"presence/user3","topic":"presence/user3","qos":"0","datatype":"auto","broker":"fee361e0.b1e17","x":680,"y":480,"wires":[["bb002127.c3376"]]},{"id":"92794687.d5e118","type":"mqtt in","z":"e0922890.07e998","name":"presence/user4","topic":"presence/user4","qos":"0","datatype":"auto","broker":"fee361e0.b1e17","x":690,"y":540,"wires":[["134da60c.97337a"]]},{"id":"9f217ff4.0b68c","type":"ui_text","z":"e0922890.07e998","group":"d86c0487.5aa878","order":0,"width":0,"height":0,"name":"","label":"User2","format":"{{msg.payload}}","layout":"row-left","x":1030,"y":420,"wires":[]},{"id":"e42147d3.0ecf08","type":"ui_text","z":"e0922890.07e998","group":"d86c0487.5aa878","order":0,"width":0,"height":0,"name":"","label":"User3","format":"{{msg.payload}}","layout":"row-left","x":1050,"y":480,"wires":[]},{"id":"44adb680.16ba08","type":"ui_text","z":"e0922890.07e998","group":"d86c0487.5aa878","order":0,"width":0,"height":0,"name":"","label":"User4","format":"{{msg.payload}}","layout":"row-left","x":1070,"y":540,"wires":[]},{"id":"8527e24b.d15f8","type":"change","z":"e0922890.07e998","name":"","rules":[{"t":"change","p":"payload","pt":"msg","from":"true","fromt":"str","to":"<font color = \"green\" i class=\"fa fa-check\"></i>","tot":"str"},{"t":"change","p":"payload","pt":"msg","from":"false","fromt":"str","to":"<font color = \"red\" i class=\"fa fa-times\"></i>","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":840,"y":360,"wires":[["439195c3.4c403c"]]},{"id":"ad98ccd8.78574","type":"change","z":"e0922890.07e998","name":"","rules":[{"t":"change","p":"payload","pt":"msg","from":"true","fromt":"str","to":"<font color = \"green\" i class=\"fa fa-check\"></i>","tot":"str"},{"t":"change","p":"payload","pt":"msg","from":"false","fromt":"str","to":"<font color = \"red\" i class=\"fa fa-times\"></i>","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":860,"y":420,"wires":[["9f217ff4.0b68c"]]},{"id":"bb002127.c3376","type":"change","z":"e0922890.07e998","name":"","rules":[{"t":"change","p":"payload","pt":"msg","from":"true","fromt":"str","to":"<font color = \"green\" i class=\"fa fa-check\"></i>","tot":"str"},{"t":"change","p":"payload","pt":"msg","from":"false","fromt":"str","to":"<font color = \"red\" i class=\"fa fa-times\"></i>","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":880,"y":480,"wires":[["e42147d3.0ecf08"]]},{"id":"134da60c.97337a","type":"change","z":"e0922890.07e998","name":"","rules":[{"t":"change","p":"payload","pt":"msg","from":"true","fromt":"str","to":"<font color = \"green\" i class=\"fa fa-check\"></i>","tot":"str"},{"t":"change","p":"payload","pt":"msg","from":"false","fromt":"str","to":"<font color = \"red\" i class=\"fa fa-times\"></i>","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":900,"y":540,"wires":[["44adb680.16ba08"]]},{"id":"fee361e0.b1e17","type":"mqtt-broker","name":"mqtt local broker","broker":"192.168.1.199","port":"1883","clientid":"presencedetector","usetls":false,"compatmode":true,"keepalive":"60","cleansession":false,"birthTopic":"","birthQos":"0","birthPayload":"","closeTopic":"","closeQos":"0","closePayload":"","willTopic":"","willQos":"0","willPayload":""},{"id":"d86c0487.5aa878","type":"ui_group","name":"Who's Home?","tab":"a404e75d.99def8","disp":true,"width":"6","collapse":true},{"id":"a404e75d.99def8","type":"ui_tab","name":"Home","icon":"dashboard","disabled":false,"hidden":false}]
6 comments:
This is great! Would you be so kink as to post the flow?
Please post you node-red flow, this is great!
Very thankful for you write-up and providing the flow! thxxxxxx!!!
Could this also be re-worked to identify which AP you were connected to (in the case of multiple APs) and then use this in a home automation scenario to automate turning on/off devices?
@BigWheels, That should be possible. I cannot check right now, though, as my automation is broken. I've updated to UniFi v7 and the node I'm using only supports up to v6. I'm getting an api error :-(
I've asked on github if there are any plans to support v7. If it gets fixed, I'll add clarification on your query.
Using Node Red and Unifi to do presence detection has been quite frustrating.
I'm using the latest version of the Node Red, the Unifi controller (via Docker), and node-red-contrib-unifi.
After some troubleshooting I was able to get information out of all the functions, BUT:
- `AllUsers` gives back all devices that have ever been seen, but it's `last_seen` is wrong
- `ClientDevices` gives back all devices that are currently connected and the `last_seen` is correct, HOWEVER devices drop off this list the moment they disconnect.
That means that neither option is viable if you want a buffer of time between `present` and `not present`, and unfortunately as I've found out: many phones constantly disconnect from wifi for a few seconds to save power and you therefore need that buffer for reliable presence detection.
Post a Comment