This python script is designed to run as a service on a Raspberry Pi. This script checks to see if specified bluetooth devices are in range and send REST messages to Home Assistant or MQTT messages to a broker regarding their status.
- You should be running Raspian Buster or later.
- Python3 is required. If you are using the default Bluetooth tracker, it will only work with Python versions below 3.10. If you are on Python 3.10 or later, you'll have to use the Bluetooth LE tracker.
If you using the Bluetooth LE tracker option, this will work on OSX and probably Windows too. On OSX you have to use the devices UUID instead of MAC address. See the Bleak Python docs for more information on that.
For the script to work properly, you need to install a few things first:
sudo apt install bluetooth libbluetooth-dev
You will also need a bluetooth Python library. The default tracker uses the older pybluez module. This isn't well maintained any longer and won't work with Python 3.10 or later.
sudo pip3 install pybluez
There is a Bluetooth LE tracker available as well. To use that you need to install the Bleak python library. Bleak is a more modern and supported Bluetooth library, so you should use this if at all possible.
sudo pip3 install bleak
If you are going to use an MQTT broker to communicate status, you will also need the following:
sudo pip3 install paho-mqtt
If you want to use the Watchdog service to restart the script if needed, you will also need the following:
sudo pip3 install sdnotify
It is recommended you install this script in /home/pi
. The service file you'll install later assumes this, so if you install it somewhere else, you'll need to edit presence.tracker.service.
For the script to do anything, you need to create a settings file. In the script directory if there is not yet a data
folder, create it and then create a file called settings.py
. It must have the following at a minimum:
devices = [{"name":"Device 1", "mac":"09:76:C5:52:2E:E6"},
{"name":"Device 2", "mac":"04:35:C6:19:C7:3D"}]
host = '127.0.0.1'
rest_token = 'your HA long lived token'
The devices dict can have an optional entity_id
item if you want to have a specific entity_id in Home Assistant rather than one created from the name. With the optional item, the devices dict will look like this:
devices = [{"name":"Device 1", "entity_id":"dev1", "mac":"09:76:C5:52:2E:E6"},
{"name":"Device 2", "entity_id":"dev2", "mac":"04:35:C6:19:C7:3D"}]
You can also mix and match within the dict, so some entries can have an entity_id
and others can omit it.
For more information about the HA long lived token, please see rest_token
below. If you are using an MQTT broker, you do not need to include rest_token
in the settings. There are a number of options available in the settings:
-
which_tracker = <str>
(defaultbluetooth
)
The tracker type that should be used. The script also supports a Bluetooth LE tracker by usingbluetooth_le
for this option. The Bluetooth LE tracker is coded using the Bleak python module. Bleak is a more modern and supported Bluetooth library, so you should use that if at all possible. -
which_notifier = <str>
(defaultharest
)
The notifier to use. If you leave this as the default, you must specific a Home Assistant token for use with the rest API in therest_token
setting. To use an MQTT broker, change this tomqtt
. -
devices = <list>
(default[]
)
This is a list of devices for which to scan. The device name can be whatever you like. The mac item is the Bluetooth MAC address for the device. -
host = <str>
(default127.0.0.1
)
The IP address of your Home Assistant server or MQTT broker. -
rest_port = <int>
(default8123
)
The port of your Home Assistant server. -
rest_token = <str>
(defaultemptry string
)
The API token from your Home Assistant server. For information on generating a token, see Home Assistant User Profiles. -
mqtt_port = <int>
(default1883
)
The port of your MQTT broker. -
mqtt_user = <str>
(defaultmqtt
)
The username needed if authentication is required for your MQTT broker. -
mqtt_pass = <str>
(defaultmqtt_password
)
The password needed if authentication is required for your MQTT broker. -
mqtt_clientid = <str>
(defaultpresencetracker
)
The client ID provided to the MQTT broker. -
mqtt_path = <str>
(defaulthomeassistant/presence_tracker
)
The root topic sent to your MQTT broker. The default is configured so that you can use MQTT Discovery with Home Assistant. -
mqtt_discover = <boolean>
(defaultTrue
)
This tells presence tracker whether or not to send the presence_tracker config to Home Assistant. If you set this toFalse
, Home Assistant will not automatically create entities. -
mqtt_qos = <int>
(default0
)
By default the script uses quality of service level 0 to talk to the broker. You can change that with this setting to1
or2
. -
mqtt_version = <str>
(defaultv5
)
By default the script uses MQTT protocol version 5 to talk to the broker. If you want to use an older version, you can usev311
orv31
. -
tracker_location = <str>
(defaultempty string
)
The location of your tracker. This is included in the name of the sensor to uniquely identify a device/tracker combination (in case you have more than one presence tracker in the house). -
home_state = <str>
(defaulthome
)
The state that the tracker sends if the device is found. -
away_state = <str>
(defaultnot_home
)
The state that the tracker sends if the device not is found. -
waittime = <float>
(default5
)
The number of minutes the tracker waits before checking for devices again. You can use decimals, so if you want something under a minute, you can use an entry like0.5
. -
bt_timeout = <int>
(default3
)
If you are using the standard Bluetooth tracker, this is the amount of time in seconds the bluetooth tracker will wait for a response from the Bluetooth subsystem before aborting. -
bt_expire = <int>
(default60
)
If you are using the Bluetooth LE tracker, this is the amount of time in seconds tracker will cache the results list. The list is cached because it takes several seconds to generate, so you don't want to have to regenerate it for every device you are tracking. If you setwaittime
to less than a minute, you will need to reduce this item so that the cache is purged after each check. -
device_name = <str>
(defaultPresence Tracker
)
The name of the device. If you run multiple presence trackers in a house, by default all tracker entities will show up under one device (when using the MQTT notifier). If you want separate devices for each presence tracker, you need to change the name of the device. You also need to change the identifier (below). -
device_identifier = <str>
(defaultWayia8kt8ZWe23xCDKBuw8nxP2cjuYkQoHogWYQy
)
The "serial number" of the device. If you run multiple presence trackers in a house, by default all tracker entities will show up under one device (when using the MQTT notifier). If you want separate devices for each presence tracker, you need to change the identifier of the device. You also need to change the name (above). -
device_version = <str>
The version number of the device (when using MQTT notifier). This is bumped automatically anytime the script is updated. I have no idea why you would want to override this, but you can. -
device_manufacturer = <str>
(defaultpkscout
)
The manufacturer of the device (when using MQTT notifier). This is my github handle by default, but if you don't want to see my handle on the device, you can change this. -
device_model = <str>
(defaultPT1000
)
The model of the device (when using MQTT notifier). -
device_config_url = <str>
(defaulthttps://github.com/pkscout/presence.tracker
)
The configuration URL for the device (when using MQTT notifier). This gives you a link in the device to the README for the script in case you need to reference it. If you don't want to link in your Home Assistant device, just set this to an empty string. -
use_watchdog = <bool>
(defaultFalse
)
If you want to use WATCHDOG to restart the script if it hangs, change this toTrue
. -
logbackups = <int>
(default1
)
The number of days of logs to keep. -
debug = <boolean>
(defaultFalse
)
For debugging you can get a more verbose log by setting this to True.
If you are using the Bluetooth LE tracker, that uses a more modern version of the Bluetooth protocol that does something called MAC address randomization. In order to keep unknown devices from tracking you via Bluetooth, devices randomize the MAC address advertised. While this is great for privacy, it makes it hard to use the device for presence tracking. To deal with that, you have to pair the device you want to track with the Pi running the presence tracker. It doesn't have to stay connected (or even reconnect). It just needs to be paired. That allows the Pi and the device you want to track to exchange some keys so that the Pi can get the actual physical MAC address of the Bluetooth on the tracked device. Unfortunately, the pairing doesn't always exchange the keys properly, so you might have to unpair and try again. In my experience, this is very hit or miss. I had one device exchange keys the first try. I had another than took a half a dozen tries.
On the Raspberry Pi you need to run the bluetooth tool as root by using sudo bluetoothctl
. The "as root" part is very important. If you don't do that, the keys won't get exchanged properly, and you'll never be able to match the static Bluetooth address in your settings with the tracked device. Once you do that, issue the following commands:
discoverable on
pairable on
agent on
From the device you want to track (probably a phone), open up the Bluetooth settings and follow the normal process for pairing a device. You should see the Raspberry Pi in the list of devices to which you can pair. During the process, you'll need to confirm you see the same code on the phone as on the Pi. Answer yes
on the Pi and accept on your phone. On the Pi you;ll see a bunch of services registering and then another question confirming that it's OK to add them. Answer yes
again. The devices are now paired. To finish, issue the following commands:
discoverable off
exit
You should now be back at the main command prompt. To test this, go to the presence.tracker folder and run the test script using python3 test.py
. You'll get a list back of every device found. If the key exchange worked, you should see your device name and the actual MAC address in the list somewhere. If you don't see your device, the keys didn't exchange. You'll need to remove the device and then pair again.
sudo bluetoothctl
paired-devices
This will get you a list of the paired devices and their MAC addresses. With that you can remove the device.
remove <MAC ADDRESS>
exit
You'all also need to forget the presence tracker device on your phone. Go back to the beginning of this section and try pairing again. Keep trying until the test.py
shows you your device.
To run from the terminal (for testing): python3 /home/pi/presence.tracker/execute.py
To exit: CNTL-C
Running from the terminal is useful during initial testing, but once you know it's working the way you want, you should set it to autostart. To do that you need to copy one of the two scripts to the systemd directory, change the permissions, and configure systemd.
To use with watchdog, from a terminal window:
sudo cp -R /home/pi/presence.tracker/presence.tracker.service.watchdog.txt /lib/systemd/system/presence.tracker.service
sudo chmod 644 /lib/systemd/system/presence.tracker.service
sudo systemctl daemon-reload
sudo systemctl enable presence.tracker.service
To use without watchdog, from a terminal window:
sudo cp -R /home/pi/presence.tracker/presence.tracker.service.txt /lib/systemd/system/presence.tracker.service
sudo chmod 644 /lib/systemd/system/presence.tracker.service
sudo systemctl daemon-reload
sudo systemctl enable presence.tracker.service
From now on the script will start automatically after a reboot. If you want to manually stop, start, or restart the service you can do that as well. From a terminal window:
sudo systemctl stop presence.tracker.service
sudo systemctl start presence.tracker.service
sudo systemctl restart presence.tracker.service
If you change anything in the settings.py
file, you will need to restart the service.
By default the presence tracker is set to send its information to Home Assistant via a REST API call. As noted in that link, if you are not using the frontend
in your setup, you will need to add the API integration. For this to work, you must also provide a Long Lived Token via the rest_token
option in the settings file.
There is no further configuration required in home assistant. The devices and occupancy state will appear in your home assistant setup. The sensor will have a name like sensor.device1_location
with a friendly name of Device 1 Location
.
Note that after a restart of Home Assistant, these sensors will show as unavailable. They will become available once the presence tracker sends its next status update.
By default, the MQTT notifier uses MQTT Discovery to create presence_tracker entities in Home Assistant. If you turn that off, you can manually create sensors in Home Assistant use the MQTT Sensor Component. You should also change the mqtt_path
to something else (like PresenceTracker/
). A sample configuration is below (based on default settings plus the sample devices):
mqtt:
sensor:
- state_topic: "PresenceTracker/device1"
name: "Device 1 Presence"
- state_topic: "PresenceTracker/device2"
name: "Device 2 Presence"
If you want to use the built-in home assistant presence features, you have to have presence trackers for each device and assign the new presence tracker(s) to a person.
If you are using the REST notifier or turn of MQTT Discovery in the MQTT notifier, you also have to create an automation to update the presence trackers. To manually create presence trackers you need to add them to the known_devices.yaml
file (or create the file if it doesn't exist) in your Home Assistant config directory. The MAC entry doesn't really matter, but I use the same MAC address as the device just to keep track of things.
device1_main:
name: Device 1 Main
mac: 09:76:C5:52:2E:E6
track: true
device2_main:
name: Device 2 Main
mac: 04:35:C6:19:C7:3D
track: true
This will create two presence trackers named device_tracker.device1_main
and device_tracker.device2_main
.
The automation uses the device_tracker.see
service to update a device tracker. As written, any update to any of the REST sensors will trigger an update of them all (the logic is easier, and there's no harm in updating something with the same status it already has).
alias: Set Presence
description: ''
trigger:
- platform: state
entity_id:
- sensor.device1_main_presence
- sensor.device2_main_presence
condition: []
action:
- service: device_tracker.see
data:
dev_id: device1_main
location_name: '{{ states(''sensor.device1_main_presence'') }}'
- service: device_tracker.see
data:
dev_id: device2_main
location_name: '{{ states(''sensor.device2_main_presence'') }}'
mode: single
Once this is running and the device trackers are assigned to a person, you'll be able to use all the built-in Home Assistant presence features. For instance, I use the numeric state
of the Home
entity to determine if anyone is home.
If you are using the Home Assistant Presence features, you can just add the devices from each pi to the right person. A person is considered home as long as at least one of their devices is home. If you aren't using the presence features, you will probably need an automation with some logic to change the state of a switch to determine occupancy.