Presence monitoring using Bluetooth

After a longer evaluation period, I felt I’m convinced sharing my setup. It will eventually help others looking for a reliable solution for presence detection using the plain Bluetooth available in most modern mobile devices

Another good part is also that you do not need to install any additional software on the mobile devices. Only Bluetooth needs to be ON.

The basis for my setup is the monitor script kindly provided to the community by Mr Andrew J Freyer

The first thing you need to do is to follow the instruction there how to install & edit configuration files. But DO NOT start monitor yet until you have upgraded to the latest beta version described below. I will also give you my settings & modifications I found useful

One thing I like to point out early is that I use a RPi3 with an external Bluetooth 4.0 dongle that is said to have a “little better” range than the built-in variant. In my case the range is about 20 meters

During the installation of monitor, I added a step for upgrading to latest beta:

  1. Upgrade to latest beta
#install git
cd ~
sudo apt-get install git

#clone this repo
git clone git://github.com/andrewjfreyer/monitor

#enter monitor directory
cd monitor/

#upgrade to latest beta
git checkout beta

Then follow the rest of the remaining instruction

By default, the setup is assuming that the monitor script shall run as a service. This might be fine but I like to run it differently, I like to have a view where I can see the progress and how it works in reality. Therefore, I have created a Node-RED flow that controls & monitors the script instead of using systemd. Node-RED also initiates all ARRIVAL and DEPARTURE scans

Before we proceed, I also recommend you to install xterm. This will allow you to set more (geometry) parameters for the window than what you can do with lxterminal. Lxterminal will work fine but the configuration of exec nodes will look slightly different

sudo apt-get install xterm
  1. Stop & disable the service:
sudo systemctl stop monitor.service
sudo systemctl disable monitor.service
  1. Run the monitor manually in a terminal a couple of times until all configuration files have been created and the script keeps running. Then stop monitor

  2. Edit your configuration files. Add your mobile devices to known_static_addresses and if you use beacons, configure this as well. Here are my main configuration files:

(behavior_preferences)

# ---------------------------
#								
# BEHAVIOR PREFERENCES
#								
# ---------------------------

#DELAY BETWEEN SCANS OF DEVICES
PREF_INTERSCAN_DELAY=1

#DETERMINE HOW OFTEN TO CHECK FOR A DEPARTED DEVICE OR AN ARRIVED DEVICE
PREF_CLOCK_INTERVAL=10

#DEPART SCAN INTERVAL
PREF_DEPART_SCAN_INTERVAL=1

#ARRIVE SCAN INTERVAL
PREF_ARRIVE_SCAN_INTERVAL=1

#MAX RETRY ATTEMPTS FOR ARRIVAL
PREF_ARRIVAL_SCAN_ATTEMPTS=1

#MAX RETRY ATTEMPTS FOR DEPART
PREF_DEPART_SCAN_ATTEMPTS=5

#DETERMINE NOW OFTEN TO REFRESH DATABASES TO REMOVE EXPIRED DEVICES
PREF_DATABASE_REFRESH_INTERVAL=35

#PERIOD AFTER WHICH A RANDOM BTLE ADVERTISEMENT IS CONSIDERED EXPIRED
PREF_RANDOM_DEVICE_EXPIRATION_INTERVAL=45

#AMOUNT AN RSSI MUST CHANGE (ABSOLUTE VALUE) TO REPORT BEACON AGAIN
PREF_RSSI_CHANGE_THRESHOLD=10

#BLUETOOTH ENVIRONMENTAL REPORT FREQUENCY
PREF_ENVIRONMENTAL_REPORT_INTERVAL=300

#SECONDS UNTIL A BEACON IS CONSIDERED EXPIRED
PREF_BEACON_EXPIRATION=145

#SECONDS AFTER WHICH A DEPARTURE SCAN IS TRIGGERED
PREF_PERIODIC_FORCED_DEPARTURE_SCAN_INTERVAL=240

#PREFERRED HCI DEVICE
PREF_HCI_DEVICE='hci0'

#COOPERATIVE DEPARTURE SCAN TRIGGER THRESHOLD
PREF_COOPERATIVE_SCAN_THRESHOLD=25

#MINIMUM TIME BEWTEEN THE SAME TYPE OF SCAN (ARRIVE SCAN, DEPART SCAN)
PREF_MINIMUM_TIME_BETWEEN_SCANS=1

#SHOULD REPORT START/END OF KNOWN DEVICE SCANS
PREF_MQTT_REPORT_SCAN_MESSAGES=true

(mqtt_preferences)

# ---------------------------
#								
# MOSQUITTO PREFERENCES
#								
# ---------------------------

# IP ADDRESS OR HOSTNAME OF MQTT BROKER
mqtt_address=127.0.0.1

# MQTT BROKER USERNAME (OR BLANK FOR NONE)
mqtt_user=''

# MQTT BROKER PASSWORD (OR BLANK FOR NONE)
mqtt_password=''

# MQTT PUBLISH TOPIC ROOT 
mqtt_topicpath=monitor

# PUBLISHER IDENTITY 
mqtt_publisher_identity='242'

# MQTT PORT 
mqtt_port='1883'

# MQTT CERTIFICATE FILE (LEAVE BLANK IF NONE)
mqtt_certificate_path=''

#MQTT VERSION (LEAVE BLANK FOR DEFAULT; EXAMPLE: 'mqttv311')
mqtt_version=''
  1. To start the monitor script, you have to be in the monitor directory. So I wrote a small bash script, startMonitor.sh, in /home/pi that does exactly that. Make that script executable. This is then executed by the exec node. The script content is like this
 
cd /home/pi/monitor
./monitor.sh -ta -td -m

Now it is time to look into the Node-RED flow. The first tab seen here is used to start monitor correctly at boot or during runtime. Also first killing an earlier instance if it is already running
image

In the second tab, we are monitoring that the script is running correctly and if it fails, we try to restart it. If that should fail, we reboot the Pi and hope for the best
image
To make this work correctly you need to set the correct id in the switch node, you shall use the same id you configured in mqtt_publisher_identity (in my case 242)
See picture below

The third tab holds the actual logic. Here we are initiating ARRIVAL and DEPARTURE scans. In my flow, I’m simply checking if any of the mobile devices is at home or if all are away, empty house. If house is empty, I just do ARRIVAL scans

One additional thing you need to do is to configure the switch node so that it only passes on messages having an allowed mac address that is in your configuration file
See picture below

At the end, the flow will publish the status ‘atHome’ or ‘emptyHouse’ to a MQTT broker. This status can be used in an automation system for many scenes or scenarios. In my case, I use the ‘atHome’ state to disable camera recordings when I have been away from our house and return back home. It has been working perfect, no doubt, very reliable (this actually made me skip using Owntracks).

Other scenarios could involve turning lights & music on or off or just simply push notifications to you that your kids are home, back from school
image
image

The flow:

[{"id":"223ee7c3.f20be8","type":"tab","label":"BLE init","disabled":false,"info":""},{"id":"d9df6880.17d7e8","type":"tab","label":"BLE control","disabled":false,"info":""},{"id":"1b64e91e.cb9b67","type":"tab","label":"BLE departures & arrivals","disabled":false,"info":""},{"id":"f9312517.b83718","type":"mqtt-broker","z":"","broker":"127.0.0.1","port":"1883","clientid":"","usetls":false,"compatmode":true,"keepalive":"60","cleansession":true,"birthTopic":"","birthQos":"0","birthPayload":"","willTopic":"","willQos":"0","willPayload":""},{"id":"e33d8fd9.d97fd","type":"mqtt in","z":"d9df6880.17d7e8","name":"","topic":"monitor/scan/#","qos":"2","broker":"f9312517.b83718","x":160,"y":100,"wires":[["4b3aa9f6.429e98"]]},{"id":"c5ebab9.c502858","type":"trigger","z":"d9df6880.17d7e8","op1":"BLE Monitor is running","op2":"BLE Monitor stopped","op1type":"str","op2type":"str","duration":"5","extend":true,"units":"min","reset":"","name":"","x":550,"y":160,"wires":[["2fe976e8.71e9ca","8f096d33.3bac2"]]},{"id":"4b3aa9f6.429e98","type":"switch","z":"d9df6880.17d7e8","name":"","property":"payload","propertyType":"msg","rules":[{"t":"eq","v":"{\"identity\":\"242\"}","vt":"str"}],"checkall":"true","outputs":1,"x":350,"y":100,"wires":[["c5ebab9.c502858","aa84294b.b5af08","d9e95bc3.faff48"]]},{"id":"7f236007.2fdde","type":"mqtt in","z":"1b64e91e.cb9b67","name":"","topic":"monitor/#","qos":"2","broker":"f9312517.b83718","x":500,"y":60,"wires":[["44bddd96.4d1134"]]},{"id":"44bddd96.4d1134","type":"switch","z":"1b64e91e.cb9b67","name":"","property":"payload","propertyType":"msg","rules":[{"t":"cont","v":"D0:C5:F3:XX:YY:ZZ","vt":"str"}],"checkall":"true","outputs":1,"x":500,"y":120,"wires":[["dc54e7da.610538"]]},{"id":"f0291899.82ab78","type":"change","z":"1b64e91e.cb9b67","name":"","rules":[{"t":"set","p":"payload","pt":"msg","to":"payload.confidence","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":530,"y":300,"wires":[["af1970.940b669"]]},{"id":"dc54e7da.610538","type":"json","z":"1b64e91e.cb9b67","name":"","pretty":false,"x":500,"y":180,"wires":[["f0291899.82ab78","a7ff6193.697f4"]]},{"id":"a7ff6193.697f4","type":"change","z":"1b64e91e.cb9b67","name":"","rules":[{"t":"set","p":"payload","pt":"msg","to":"payload.name","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":530,"y":240,"wires":[["af1970.940b669"]]},{"id":"af1970.940b669","type":"join","z":"1b64e91e.cb9b67","name":"","mode":"custom","build":"string","property":"payload","propertyType":"msg","key":"topic","joiner":" ","joinerType":"str","accumulate":false,"timeout":"","count":"2","x":500,"y":360,"wires":[["71e6f30.e0b140c"]]},{"id":"a9187f48.36279","type":"file","z":"1b64e91e.cb9b67","name":"","filename":"/home/pi/monitor.txt","appendNewline":true,"createDir":false,"overwriteFile":"false","x":1080,"y":300,"wires":[[]]},{"id":"2beedcfa.5bdeb4","type":"function","z":"1b64e91e.cb9b67","name":"","func":"var d = new Date();\nvar hours = d.getHours();\nvar minutes = d.getMinutes();\nvar seconds = d.getSeconds();\nvar year = d.getFullYear();\nvar month = d.getMonth()+1;\nif (month < 10) {\n    month = '0'+ month;\n}\nvar day = d.getDate();\nif (day < 10) {\n    day = '0'+ day;\n}\nif (hours < 10) {\n    hours = '0'+ hours;\n}\nif (minutes < 10) {\n    minutes = '0'+ minutes;\n}\nvar global_n = year+'-'+month+'-'+day+' '+hours+':'+minutes+':'+seconds;\nmsg.payload = global_n+' '+msg.payload;\nreturn msg;","outputs":1,"noerr":0,"x":790,"y":300,"wires":[["a9187f48.36279","1108e38a.6863ec"]]},{"id":"71e6f30.e0b140c","type":"rbe","z":"1b64e91e.cb9b67","name":"","func":"rbe","gap":"","start":"","inout":"out","x":790,"y":360,"wires":[["2beedcfa.5bdeb4"]]},{"id":"3743bb87.b33584","type":"inject","z":"1b64e91e.cb9b67","name":"","topic":"","payload":"go","payloadType":"str","repeat":"10","crontab":"","once":false,"x":130,"y":60,"wires":[["7e6f1ffd.69daf"]]},{"id":"34eba29f.d5d9ee","type":"mqtt out","z":"1b64e91e.cb9b67","name":"","topic":"monitor/scan/arrive","qos":"1","retain":"","broker":"f9312517.b83718","x":170,"y":300,"wires":[]},{"id":"927e4bda.1d55e8","type":"mqtt out","z":"1b64e91e.cb9b67","name":"","topic":"monitor/scan/depart","qos":"1","retain":"","broker":"f9312517.b83718","x":180,"y":360,"wires":[]},{"id":"1108e38a.6863ec","type":"function","z":"1b64e91e.cb9b67","name":"","func":"// initialise the variables if they doesn't exist already\nflow.set('atHome', flow.get('atHome')||0);\nvar phones = flow.get('phones@home')||{};\nvar res = msg.payload.split(' ');\n\nif(parseInt(res[2])>0){\n    phones[res[3]]=res[2];\n    flow.set('atHome', 1);\n}\nif(parseInt(res[2])===0){\n    delete phones[res[3]];\n    var size = Object.keys(phones).length;\n    if(size===0){\n        flow.set('atHome', 0);\n    }\n        \n}\nflow.set('phones@home', phones);\n\nif(flow.get('atHome')===1){\n    msg.payload = 'atHome';\n}\nif(flow.get('atHome')===0){\n    msg.payload = 'emptyHouse';\n}\n\nreturn msg;\n","outputs":1,"noerr":0,"x":790,"y":240,"wires":[["bf58f1eb.fc5a1"]]},{"id":"7e6f1ffd.69daf","type":"switch","z":"1b64e91e.cb9b67","name":"","property":"atHome","propertyType":"flow","rules":[{"t":"eq","v":"0","vt":"num"},{"t":"eq","v":"1","vt":"num"}],"checkall":"true","outputs":2,"x":130,"y":120,"wires":[["3ee98d7c.9613b2"],["f5215709.e4f368"]]},{"id":"40903e5b.82b5c","type":"file in","z":"1b64e91e.cb9b67","name":"","filename":"/home/pi/monitor.txt","format":"lines","chunk":false,"sendError":false,"x":550,"y":460,"wires":[["5a85d2e1.e9803c"]]},{"id":"e79f78ac.3f7138","type":"inject","z":"1b64e91e.cb9b67","name":"","topic":"","payload":"Show log","payloadType":"str","repeat":"","crontab":"","once":false,"x":140,"y":460,"wires":[["40903e5b.82b5c"]]},{"id":"5a85d2e1.e9803c","type":"debug","z":"1b64e91e.cb9b67","name":"","active":true,"console":false,"complete":"false","x":810,"y":460,"wires":[]},{"id":"f5215709.e4f368","type":"delay","z":"1b64e91e.cb9b67","name":"","pauseType":"rate","timeout":"5","timeoutUnits":"seconds","rate":"1","nbRateUnits":"30","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":true,"x":160,"y":240,"wires":[["927e4bda.1d55e8"]]},{"id":"3ee98d7c.9613b2","type":"delay","z":"1b64e91e.cb9b67","name":"","pauseType":"rate","timeout":"5","timeoutUnits":"seconds","rate":"1","nbRateUnits":"10","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":true,"x":160,"y":180,"wires":[["34eba29f.d5d9ee"]]},{"id":"7a6a55a1.a538bc","type":"file","z":"1b64e91e.cb9b67","name":"","filename":"/home/pi/monitor.txt","appendNewline":true,"createDir":false,"overwriteFile":"delete","x":570,"y":510,"wires":[[]]},{"id":"4c3c0de3.cdc784","type":"inject","z":"1b64e91e.cb9b67","name":"","topic":"","payload":"Delete log","payloadType":"str","repeat":"","crontab":"","once":false,"x":140,"y":510,"wires":[["7a6a55a1.a538bc"]]},{"id":"bf58f1eb.fc5a1","type":"rbe","z":"1b64e91e.cb9b67","name":"","func":"rbe","gap":"","start":"","inout":"out","x":790,"y":180,"wires":[["529415aa.a466bc"]]},{"id":"529415aa.a466bc","type":"mqtt out","z":"1b64e91e.cb9b67","name":"","topic":"Presence","qos":"","retain":"","broker":"f9312517.b83718","x":1040,"y":180,"wires":[]},{"id":"1707f9d6.7248e6","type":"exec","z":"d9df6880.17d7e8","command":"sudo reboot","addpay":false,"append":"","useSpawn":"","timer":"","oldrc":false,"name":"Reboot","x":780,"y":340,"wires":[[],[],[]]},{"id":"91e9e803.371278","type":"comment","z":"d9df6880.17d7e8","name":"Watchdog","info":"","x":140,"y":60,"wires":[]},{"id":"2fe976e8.71e9ca","type":"trigger","z":"d9df6880.17d7e8","op1":"","op2":"1","op1type":"nul","op2type":"str","duration":"5","extend":true,"units":"min","reset":"","bytopic":"all","name":"","x":790,"y":220,"wires":[["1707f9d6.7248e6"]]},{"id":"aa84294b.b5af08","type":"change","z":"d9df6880.17d7e8","name":"","rules":[{"t":"set","p":"reset","pt":"msg","to":"true","tot":"bool"}],"action":"","property":"","from":"","to":"","reg":false,"x":560,"y":220,"wires":[["2fe976e8.71e9ca"]]},{"id":"d9e95bc3.faff48","type":"debug","z":"d9df6880.17d7e8","name":"","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","x":550,"y":100,"wires":[]},{"id":"8f096d33.3bac2","type":"link out","z":"d9df6880.17d7e8","name":"","links":["bcb16dbb.9488f"],"x":755,"y":160,"wires":[]},{"id":"edc18f2b.aea67","type":"exec","z":"223ee7c3.f20be8","command":"export DISPLAY=:0 && uxterm -geometry 96x24-150+150 -e bash /home/pi/startMonitor.sh","addpay":false,"append":"","useSpawn":"false","timer":"","oldrc":false,"name":"","x":800,"y":100,"wires":[[],[],[]]},{"id":"6e6c2971.ffaa18","type":"inject","z":"223ee7c3.f20be8","name":"","topic":"","payload":"true","payloadType":"bool","repeat":"","crontab":"","once":true,"onceDelay":"10","x":130,"y":100,"wires":[["5aeecfbe.feb0b","c893a8af.ca2b38"]]},{"id":"c893a8af.ca2b38","type":"exec","z":"223ee7c3.f20be8","command":"pkill -f xterm","addpay":false,"append":"","useSpawn":"false","timer":"","oldrc":false,"name":"","x":330,"y":220,"wires":[[],[],[]]},{"id":"31a0fce.5057d04","type":"comment","z":"223ee7c3.f20be8","name":"Starting services","info":"","x":160,"y":60,"wires":[]},{"id":"5aeecfbe.feb0b","type":"trigger","z":"223ee7c3.f20be8","op1":"","op2":"1","op1type":"nul","op2type":"str","duration":"15","extend":false,"units":"s","reset":"","bytopic":"all","name":"","x":330,"y":100,"wires":[["edc18f2b.aea67"]]},{"id":"bcb16dbb.9488f","type":"link in","z":"223ee7c3.f20be8","name":"","links":["8f096d33.3bac2"],"x":155,"y":160,"wires":[["5aeecfbe.feb0b","c893a8af.ca2b38"]]}]
5 Likes

ooo, I actually did something similar to this (though mostly within Node-Red). I was trying to solve a similar problem; using regular BLE devices to determine presence.

What I ended up with was a model where I can have scanners (any number but at least one) that would simply post to an MQTT topic what devices it sees.

Then a separate node-red processor (just one) that would read the topic and determine is any scanner saw a device (and handle the presence logic for that device).

I can't tell, but I think I can achieve that with this approach, though I am not sure. It sort of looks like the monitor script will do it's own presence determination, so I am not clear how you can aggregate several of these?

Thanks a ton, really interested in what you've done.