Using cheap Bluetooth ‘key finder’ fobs with ESPHome and Home Assistant

26-3-2023

Action sells ‘key finder’ key fobs for as low as €2-3. These key fobs are designed to be used in conjunction with a smartphone app that can do a few things. For example, you can tell the keyfob to make a sound, which comes in handy when you misplaced your keys. Depending on your smartphone you can also use the keyfob to find the smartphone – pushing the fob’s button can trigger a sound on the phone. Lastly, the keyfob can make a sound when the connection with the smartphone is lost, warning you that you may be about to misplace your keys.

These keyfobs can be very easily used with Home Assistant using ESPHome, but it takes a bit of fiddling to get right. Using ESPHome you configure a device (typically an ESP32) to act as a ‘bridge’ between Home Assistant (which it connects to over Wi-Fi) and Bluetooth LE. Bluetooth LE, often abbreviated BLE, is a ‘low energy’ version of Bluetooth designed for applications such as this one, where a device is basically powered only by a coin cell.

How does Bluetooth LE work?

BLE provides very basic functionality for identifying and connecting to devices. Devices can basically ‘expose’ certain characteristics (values that can be read from or written to), grouped by service. For instance, a heart monitoring band may expose a service ‘heart rate monitoring’ with a characteristic ‘current heart beat measured’, in addition to a service exposing the current battery state. A characteristic that can be ‘read’ can also be ‘listened to’ – you can receive an update from the device each time the characteristic is updated.

Services and characteristics are identified by a unique ID (UUID), many of which are part of the Bluetooth LE standard. The standardized UUIDs are ‘short’ (e.g. 0x1803) where as custom (proprietary) UUIDs are much longer. Many BLE devices at least expose a service providing battery info, the device name and oftentimes also a service for OTA updating of firmware.

Querying our keyfob

Our keyfobs expose a few useful services and characteristics as well, which you can see by connecting to them using an app such as LightBlue or nRF Remote (iOS) or BlueSee (macOS) or Microsoft’s Bluetooth LE Explorer. It may be a bit of a challenge finding your keyfobs among the (typically many) devices around – I can see about 30 from my living room alone. After connecting to one of the keyfobs, it shows six available services:

  • Immediate Alert (0x1802): this service exposes a single characteristic called ‘alert level’ (0x2A06) that, when set to the single byte value ‘0x01’ will cause the keyfob to start beeping. The beeping stops when the characteristic is set to ‘0x00’.
  • Tx Power (0x1804): this service allows you to read the transmit power the keyfob needs to use to reach you. This can be useful to (very roughly) determine the distance between you and the keyfob.
  • TI KeyFob Button Service (0xFFE0): this exposes various things, most importantly a characteristic that lets us listen for button presses on the keyfob (characteristic FFE1).
  • Link Loss (0x1803): this service allows you to configure whether the keyfob starts beeping when it loses its connection. It exposes a single characteristic – a byte value – that determines the behaviour (my particular keyfob appears to not quite follow the standard – writing 0xFF disables the link loss alert, any other value including 0x00 enables it).
  • Battery Information (0x180F) which, you guessed it, provides the estimated battery state.
  • A proprietary service with UUID ‘5833FF01-9B8B-5191-6142-22A4536EF123’. This may be a service used for OTA updates.
Services discovered on my keyfob
The ‘link loss’ service provides a way to configure whether the device beeps on losing connection.

One annoying thing on macOS and iOS is that the MAC address is not readily exposed (for privacy reasons, MAC addresses should rotate, and hiding the address from app developers is probably a way to prevent them from relying on the MAC address as remaining constant). In some cases (i.e. the LightBlue app) there is however a (dummy?) service and characteristic that can be queried to read the MAC address.

Using with ESPHome

To make ESPHome talk to the keyfob, a fairly complicated configuration is needed. EPHome provide a starting point here that allows for reading the button. I added the ability to disconnect the keyfob, send alerts (triggering the keyfob beep for 10 seconds) as well as the ability to configure the ‘link loss’ setting. My configuration is below:

substitutions:
  device_name: esp_ble_1

esphome:
  name: ${device_name}
  platform: ESP32
  board: esp32dev

wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password

  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: ${device_name}
    password: !secret api_password

captive_portal:

# Enable logging
logger:
  baud_rate: 0

# Enable Home Assistant API
api:
  #  password: !secret api_password
  encryption:
    key: !secret api_key

ota:
  password: !secret api_password

esp32_ble_tracker:

ble_client:
  # Replace with your tag's MAC address
  - mac_address: FF:FF:01:02:03:04
    id: itag_1
    on_connect:
      - delay: 30s
      - ble_client.ble_write:
          id: itag_1
          service_uuid: "1803"
          characteristic_uuid: "2a06"
          value: [0xFF]

binary_sensor:
  - platform: template
    id: itag_1_button
    name: "iTag 1 button"
    filters:
      delayed_off: 200ms

switch:
  - platform: ble_client
    ble_client_id: itag_1
    name: "Connect iTag 1"

  - platform: template
    name: "iTag 1 link loss alert"
    lambda: |-
      return (id(itag_1_link_loss).state != 255);
    turn_on_action:
      - ble_client.ble_write:
          id: itag_1
          service_uuid: "1803"
          characteristic_uuid: "2a06"
          value: [0x01]
    turn_off_action:
      - ble_client.ble_write:
          id: itag_1
          service_uuid: "1803"
          characteristic_uuid: "2a06"
          value: [0xFF]

button:
  - platform: template
    name: "iTag 1 alert"
    on_press:
      - ble_client.ble_write:
          id: itag_1
          service_uuid: "1802"
          characteristic_uuid: "2a06"
          value: [0x01]
      - delay: 10s
      - ble_client.ble_write:
          id: itag_1
          service_uuid: "1802"
          characteristic_uuid: "2a06"
          value: [0x00]

sensor:
  - platform: ble_client
    type: characteristic
    ble_client_id: itag_1
    name: "iTag 1 button"
    service_uuid: "ffe0"
    characteristic_uuid: "ffe1"
    notify: true
    update_interval: never
    on_notify:
      then:
        - binary_sensor.template.publish:
            id: itag_1_button
            state: ON
        - binary_sensor.template.publish:
            id: itag_1_button
            state: OFF

  - platform: ble_client
    type: characteristic
    ble_client_id: itag_1
    name: "iTag 1 Battery"
    service_uuid: "180f"
    characteristic_uuid: "2a19"
    icon: "mdi:battery"
    unit_of_measurement: "%"

  - platform: ble_client
    type: rssi
    ble_client_id: itag_1
    name: "iTag 1 RSSI"

  - platform: ble_client
    type: characteristic
    ble_client_id: itag_1
    id: itag_1_link_loss
    name: "iTag 1 Link Loss Alert"
    service_uuid: "1803"
    characteristic_uuid: "2a06"

Using this configuration, the keyfob shows up nicely in Home Assistant as a set of entities. It is fairly easy to link a button press to an automation to e.g. turn lights on or off.

Security?

Interestingly many of these BLE devices can be connected to by anyone as long as nobody has connected already. Many BLE devices expose information (innocent stuff like the battery level, but in case of a Bluetooth digital weight scale also your weight and fat percentage…) to whoever is in range, which may be undesirable. My neighbours can tell whether I ate too much and when I am brushing my teeth (to prevent blue tooth I guess :-))!

This also means that it is very easy to annoy people carrying a keyfob like this – simply connect and set the ‘immediate alert’ characteristic. I am also not too hopeful about the security of the OTA firmware upgrade procedure. Finally, it is very easy to ‘clone’ a device like this – simply collect the MAC address and exposed services and you are good to go. If you use the keyfob to turn lights on or off, anyone who can clone the keyfob can do this too (so: don’t use these keyfobs to unlock doors, turn on heavy machinery, et cetera). Many older non-Bluetooth devices (car garage door openers and the like) operating in the 433 MHz/868 MHz bands used to be similarly insecure, but more modern non-Bluetooth devices typically use solutions such as rolling codes to prevent this somewhat.

There is a privacy issue with these lower-end BLE devices as well – they broadcast their presence using a fixed unique number (the MAC address) which makes anyone carrying these keyfobs easily trackable (indeed, shops can identify you as a returning customer, the route you take in their store, etc.). More modern BLE devices (such as Apple’s AirTag and their other devices) rotate MAC addresses for privacy and contain measures to prevent e.g. stalking.