How to create a Bluetooth tracker with Home Assistant and ESPHome

by Pete
Published: Updated: 16 minutes read

Home automation systems like Home Assistant allow you to control and automate various aspects of your home with ease. One of the most useful features of Home Assistant is its device tracking capability, which allows you to track the location of your family members and pets using their smartphones or other Bluetooth-enabled devices.

In this article, we’ll show you how to create a Bluetooth device tracker with Home Assistant and ESPHome. ESPHome is a powerful and easy-to-use firmware for ESP32 boards that allows you to create custom sensors and devices. We’ll be using ESPHome to create a Bluetooth sensor that will detect the presence of Bluetooth devices and report their status to Home Assistant.

Bluetooth Compatible Tracker Keys Finder Item Locator for Keys Bags Pets Anti-lost Locator Tag

By the end of this article, you’ll have a working Bluetooth device tracker that you can use to track the location of your family members and pets. We’ll guide you through the process of configuring the Bluetooth sensor in ESPHome, flashing the firmware to your board, and integrating the device tracker with Home Assistant.

Let’s get into it

For this to work, you’ll need to the following:

  • Nodemcu ESP32 WiFi + Bluetooth ESP WROOM 32 38 pin Development Board (or equivalent) – one for each room or enough for zoning
  • One or many Bluetooth compatible tracker devices (for example these devices on AliExpress)
  • Home Assistant Installed and functioning.
  • ESPHome installed on Home Assistant

1
Logic

The idea behind this project is as follows:

  • The tracker devices, when on, will broadcast their Bluetooth MAC address. Each of the ESP32 devices will, when the device is in range, let Home Assistant know the device’s RSSI (Received signal strength indication) value.
  • Using the RSSI value, we can determine it’s location by calculating the device with the largest RSSI value – therefore, we’ll know where the device is, roughly, by the RSSI strength.
  • Each iTag will have their own “entity” for each device and each location for tracking.
  • Therefore, you’ll need to know each device’s MAC address, which is easy to find out.
  • Each iTag will need an Automation to determine the lowest RSSI value and return the “location” based on where the lowest value is returned from.
  • If you have 3 iTags, there will be 3 (almost) identical Automations. When I say almost, we’re talking about the automation structure will be identical, the referencing iTag’s entities will be relevant for only that iTag in question.

2
Finding the MAC of a Bluetooth Device

I recommend downloading a free Bluetooth scanning app for your smartphone. Something like BT Inspector works well.

  • Start Scanning to find all nearby devices
  • If you’re using the tracker devices I posted above, hold down the button until you hear a beep (it will turn on the device)
  • Watch the list of nearby BT devices in the scanner cycle and there should be a device called “iTAG” by Polaris IND at the very top (again, if you’re using BT Inspector and you’re using the iTAG device from AliExpress)
  • Tap on the iTAG device and hit Interrogate – wait a few seconds for it to connect
  • You should see a the MAC address listed under the Characteristics – mine starts with an FF:FF:10:9E prefix

3
ESPHome Configuration

Here is my split out device yaml called “esp32s06.yaml”. I learn’t the hard way my naming my devices “Hallway” or “Bedroom” then I’d move the device into another room. I found it easier to manage and one less step if I made the device name generic and used Home Assistant to “name” the locations.

# Bluetooth Hub 04 - Master Bedroom
### Substitutions ###
substitutions:
  device_name: esp32s06
  location: Master Bedroom
esphome:
  platform: ESP32
  board: nodemcu-32s
  name: $device_name
# Enable logging
logger:
# Enable Home Assistant API
api:
ota:
  password: !secret_ota
wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password
  # Optional manual IP
  manual_ip:
    static_ip: 10.0.0.49
    gateway: 10.0.0.254
    subnet: 255.255.255.0
  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: "$device_name Fallback Hotspot"
    password: !secret_hotpost_password
captive_portal:
##### Include Global Custom Files
<<: !include common/devboards/esp32/esp32_ble_tracker.yaml
<<: !include common/devboards/esp32/esp32_common.yaml
  
sensor:    
  - <<: !include common/devboards/esp32/esp32_sensor_common.yaml  
  - <<: !include common/components/itags/sensor_itag01.yaml
binary_sensor: 
  - <<: !include common/devboards/esp32/esp32_binary_common.yaml
switch: 
  - <<: !include common/devboards/esp32/esp32_switch_common.yaml
text_sensor: 
  - <<: !include common/devboards/esp32/esp32_text_common.yaml

4
esp32_ble_tracker.yaml Contents

Using this configuration, we can enable the BLE Tracker on our esp32.

 esp32_ble_tracker:
  scan_parameters:
     window: 30ms
     interval: 100ms

I use scan window to be 30ms and interval to be 100ms. Using defaults were too long in between scans and I found this an OK compromise. If you find better parameters, us them! Tinkeres have had success in changing these values and also changing the board framework to espidf. For more information on this framework, see the platformio page.

5
sensor_itag01.yaml Contents

Using this configuration, we can add iTag devices. I personally use 01, 02, etc to increment the next device in line.

  - platform: ble_rssi
    mac_address: "FF:FF:FF:FF:FF:FF"
    name: "iTag01 $location RSSI"

Note: the Name needs to have the $location added as it relates to the Substitution configuration. I use this to “map” the device location.

6
Other Configuration Items

The remaining configuration items that have been split out are as follows:

  • esp32_sensor_common.yaml
  - platform: wifi_signal
    name: "$device_name WiFi Signal Sensor"
    update_interval: 300s
  • esp32_binary_common.yaml
  - platform: status
    name: "$device_name Status"
  • esp32_switch_common.yaml
  - platform: restart
    name: "$device_name Restart"
  • esp32_text_common.yaml
  - platform: version
    name: "$device_name ESPHome Version"
#    hide_timestamp: true

7
ESPHome log output

[09:44:54][C][ble_rssi:011]: BLE RSSI Sensor 'iTag01 Master Bedroom RSSI'
[09:44:54][C][ble_rssi:011]:   Device Class: 'signal_strength'
[09:44:54][C][ble_rssi:011]:   State Class: 'measurement'
[09:44:54][C][ble_rssi:011]:   Unit of Measurement: 'dBm'
[09:44:54][C][ble_rssi:011]:   Accuracy Decimals: 0
[09:51:24][D][sensor:094]: 'iTag01 Master Bedroom RSSI': Sending state -97.00000 dBm with 0 decimals of accuracy

Looking good. -97dBm (very weak so the device must be far from the hub).

8
Creating an Input Text item

For each iTag, an associated input_text helper item will need to be created. They’ll be referenced by an automation later in this article.

My details are:

  • Name: iTag01 Location
  • Entity ID: input_text.itag01_location

9
Confirming all Home Assistant entities have been created

If all goes well, you should see a new entity appear in the entity list. Here is a list of all of my entities for iTag01.

Note: I have 7 esp32s around the house and shed. The input_text / automation will be explained shortly.

10
Automations make the magic happen

Imagine you have 7 esp32’s around the home (at the time of writing, this is what I have at home) with the same configuration as above (remember to change the Location in substitutions).

The provided YAML code is an example of an automation in Home Assistant using a time-based trigger. The automation sets the value of an input_text entity based on the received RSSI (Received Signal Strength Indicator) values from different sensors. It calculates the maximum RSSI value and determines the location based on that value.

The automation checks each sensor’s RSSI value and assigns a numeric value (-100 if unknown) to variables representing different locations. It then creates a list of all the RSSI values and finds the maximum value. Based on the maximum value, it sets the value of the input_text entity to the corresponding location.

If all RSSI values are -100, it sets the location to “Not at Home.” Otherwise, it determines the location based on the highest RSSI value. Possible locations include Shed Workshop, Master Bedroom, Hallway, Dining Room, Garage, Study, Lounge Room, or an error message if none of the conditions are met.

This automation allows for dynamic tracking of the iTag01 device’s location based on the RSSI values received from multiple sensors.

alias: iTag01 Location
description: ""
trigger:
  - platform: time_pattern
    seconds: /15
condition: []
action:
  - service: input_text.set_value
    entity_id: input_text.iTag01_location
    data_template:
      value: >-
        {% set dining_room_rssi = states('sensor.iTag01_dining_room_rssi') %} 
        {% if dining_room_rssi == 'unknown' %} 
          {% set dining_room_rssi = -100 | float %}
          {% else %} {% set dining_room_rssi = dining_room_rssi | float %}
        {% endif %} 
        {% set garage_rssi = states('sensor.iTag01_garage_rssi') %}  {% if
        garage_rssi == 'unknown' %}
          {% set garage_rssi = -100 | float %}
          {% else %} {% set garage_rssi = garage_rssi | float %}
        {% endif %} 
        {% set hallway_rssi = states('sensor.iTag01_hallway_rssi') %}  {% if
        hallway_rssi == 'unknown' %}
          {% set hallway_rssi = -100 | float %}
          {% else %} {% set hallway_rssi = hallway_rssi | float %}
        {% endif %} 
        {% set shed_workshop_rssi = states('sensor.iTag01_shed_workshop_rssi')
        %} {% if shed_workshop_rssi == 'unknown' %}
         {% set shed_workshop_rssi = -100 | float %}
         {% else %} {% set shed_workshop_rssi = shed_workshop_rssi | float %}
        {% endif %} 
        {% set master_bedroom_rssi = states('sensor.iTag01_master_bedroom_rssi')
        %}  {% if master_bedroom_rssi == 'unknown' %}
         {% set master_bedroom_rssi = -100 | float %}
         {% else %} {% set master_bedroom_rssi = master_bedroom_rssi | float %}
        {% endif %}
        {% set study_rssi = states('sensor.iTag01_study_rssi') %}  {% if
        study_rssi == 'unknown' %}
         {% set study_rssi = -100 | float %}
         {% else %} {% set study_rssi = study_rssi | float %}
        {% endif %}
        {% set lounge_room_rssi = states('sensor.itag01_lounge_room_rssi') %} 
        {% if lounge_room_rssi == 'unknown' %}
         {% set lounge_room_rssi = -100 | float %}
         {% else %} {% set lounge_room_rssi = lounge_room_rssi | float %}
        {% endif %}
        {% set sensor_values = [dining_room_rssi, garage_rssi, hallway_rssi,
        shed_workshop_rssi, master_bedroom_rssi, study_rssi] %}  {% set max_rssi
        = max([dining_room_rssi, garage_rssi, hallway_rssi, shed_workshop_rssi,
        master_bedroom_rssi, study_rssi, lounge_room_rssi]) %}
        {% if dining_room_rssi == -100 and garage_rssi == -100 and hallway_rssi
        == -100 and shed_workshop_rssi == -100 and master_bedroom_rssi == -100
        and study_rssi == -100 and lounge_room_rssi == -100 %}  
           Not at Home
          
          {% elif max_rssi == shed_workshop_rssi %}
            Shed Workshop 
          {% elif max_rssi == master_bedroom_rssi %}
            Master Bedroom 
          {% elif max_rssi == hallway_rssi %}
            Hallway
          {% elif max_rssi == dining_room_rssi %}
            Dining Room 
          {% elif max_rssi == garage_rssi %}
            Garage
          {% elif max_rssi == study_rssi %}
            Study
          {% elif max_rssi == lounge_room_rssi %}
            Lounge Room
          
          {% else %}
            Error in Locating
         {% endif %}
mode: single

11
Add a tile to Lovelace

lovelace tile

As long as the iTag is in range of any of the esp32s0x devices, the Automation will update the status of the input_text helper which we can use to see where the iTag is. Therefore, we should add a new tile to Lovelace.

type: vertical-stack
cards:
  - type: custom:mushroom-title-card
    title: Device Trackers
  - type: tile
    entity: input_text.itag01_location
    icon: mdi:key-chain
    name: Pete's Keys

In Summary

Pretty interesting project. I enjoyed making this work and learning about RSSI values, and also building automations to compare values. What’s great is that you can use this logic to track any BLE device that returns the same MAC address every time – e.g.: fitness tracker, smart watch, etc.

Note: There are some caveats to this project:

  1. The iTag turns off if out of range for a long period of time. Periodically, I need to press / hold the button to turn it back on.
  2. The iTag battery only lasts approximately 3 months.
  3. Occasionally, the automation logic gets messed up and returns the wrong value / location. I surmise this to be that the iTag doesn’t register to all ESP32 in range and therefore, not all ESP32’s know of its RSSI.
  4. Location time varies – sometimes it’s quick (depending on the BLE scan window) and also when the automation runs. Between 15s and 30s is usually the sync time
  5. There are other “Presence” projects already on the ESP32 market. Like ESPresence. The difference here is that this an ESPHome based project, and ESPresence is its own firmware / ecosystem. ESPresence is excellent so check it out.
  6. Finally, the iTag is cheap. You get what you pay for. There are other products on the market that would give you longer battery life, however cost many times more than what the iTag does.
  7. I need to add more error checking, but maybe that’s something for version 2.0 :)