Bluetooth Battery Levels (Android)

I have seen a few posts about getting the battery level of Bluetooth device in homeassistant but none of them actually fully explain and none of them actually worked for me at first.

  1. First open the homeassistant app on your phone and navigate to Settings > Companion App > Manage Sensors > Last Update Trigger.
  2. Ensure that it is enabled.
  3. Click Add New Intent at the bottom of the screen and then on Intent 1.
  4. Enter exactly android.bluetooth.device.action.BATTERY_LEVEL_CHANGED.
  5. IMPORTANT: Restart the app, it is important that you perform a forced restart (there will be a button in your phones settings on the homeassistant app info page that will say something along the lines of Force stop).
  6. Open up your configuration.yaml file and add:
template:
  - trigger:
      - platform: "event"
        event_type: "android.intent_received"
        event_data:
          android.bluetooth.device.extra.DEVICE: "<mac_address>"
          intent: "android.bluetooth.device.action.BATTERY_LEVEL_CHANGED"
    sensor:
      - name: "<device_name> Battery Level"
        unique_id: "<uid>"
        device_class: "battery"
        unit_of_measurement: "%"
        state_class: "measurement"
        availability: "{{ trigger.event['data']['android.bluetooth.device.extra.BATTERY_LEVEL'] != '-1' }}"
        state: "{{ trigger.event['data']['android.bluetooth.device.extra.BATTERY_LEVEL'] }}"
  1. Swap <mac_address> and <device_name> for the mac address and device name of your bluetooth device.
  2. Swap for a unique id (in the Studio Code Server you can do this from the right click menu).
  3. Either restart HomeAssistant or open the developer tools page and click reload template entities.
  4. You did it!

Troubleshooting:

  • Open the developer tools page and navigate to the events tab, enter android.intent_received into Event to subscribe to, then click START LISTENING and then power off and on your bluetooth device you should see two events pop up one for power off and one for power on.
  • Check the system logs in settings.
  • Check that your phone is showing a battery charge for the device in Bluetooth settings.
  • Check the mac address is correct.
  • Check all spellings!

Disclaimers

For Garmin devices check out https://community.home-assistant.io/t/home-assistant-app-for-garmin

For buds style devices, you will only get one battery level; for my pixel buds pro, that was the right ear bud battery level.

7 Likes

Had some games with this! First off, how do you find the MAC Address of your Bluetooth Device? Mine’s a Garmin watch, so you need to investigate the menus, it will be device specific for your model, and it won’t be in the obvious first place to look, like Bluetooth or network settings.

Settings 1

Settings 2

Settings 3

About

When you come to edit the YAML, here’s a hint as to where to find the “Generate UUID” at step 8

  1. Swap for a unique id (in the Studio Code Server you can do this from the right click menu).

When listening for events, make sure you put your entity in the correct input. Its the second “listen” box not the first “fire” box.

Lastly, be dissappointed that your Garmin watch will not report its battery level. There are posts on the Garmin forum from annoyed users about this oversight by Garmin: View battery status of device on Connect App. Boo Garmin!

The good news is, I got my headphones battery level to report just fine. Again you’ll have fun getting the MAC address. Here are two suggestions:

Home Assistant can assist:

The Companion App

  1. Open the Companion App from HA’s Settings menu

  2. Click Manage Senors

  3. Scroll down to see the list of deevices that are already paired

thank you for this, this is the first post I’ve seen that actually works!

For anyone wondering about the unique_id, you can just generate on in the file editor directly by selecting the settings cog in the top right corner

1 Like

Great post thank you!

For some reason this doesn’t work for me. I thought I followed the guide to the letter but must have missed something.

I added the config to the companion app and the force restarted app (even rebooted phone also)


I found out mac-address of the earphones by enabling Bluetooth connection i app sensors.


Added to configuration.yaml:

template:
  - trigger:
      - platform: "event"
        event_type: "android.intent_received"
        event_data:
          android.bluetooth.device.extra.DEVICE: "28:6F:XX:XX:EA:33"
          intent: "android.bluetooth.device.action.BATTERY_LEVEL_CHANGED"
    sensor:
      - name: "OnePlus Buds Pro 2 Battery Level"
        unique_id: "2e6dd97e-ee79-49c4-b10e-529cd1050fc3"
        device_class: "battery"
        unit_of_measurement: "%"
        state_class: "measurement"
        availability: "{{ trigger.event['data']['android.bluetooth.device.extra.BATTERY_LEVEL'] != '-1' }}"
        state: "{{ trigger.event['data']['android.bluetooth.device.extra.BATTERY_LEVEL'] }}" 

Then restart of HA.

The sensor is created but doesn’t get any battery data. Listening to android.intent_received is totally quiet. I dont see anything in logs and the phone is showing a battery charge.

Both HA and my mobile (Samsung S24+) are fully updated to the latest software.

What did I do wrong? :slight_smile:

It looks like you have an extra “.” at the end of your intent in the companion app settings

Brilliant, thank you!

1 Like

Hi team. I am stumped here. Intent is working fine but sensor never gets updated. Bluetooth MAC addresses match. Any ideas?

event_type: android.intent_received
data:
  android.bluetooth.device.extra.BATTERY_LEVEL: "100"
  android.bluetooth.device.extra.DEVICE: XX:2B:8C:C8:XX:XX
  android.bluetooth.device.extra.PREV_BATTERY_LEVEL: "-1"
  intent: android.bluetooth.device.action.BATTERY_LEVEL_CHANGED
  device_id: 02e41c6d23771a13
origin: REMOTE
time_fired: "2024-03-17T02:36:26.142004+00:00"
context:
  id: 01HS548Z4XPT2Y0AMB2QTT0NMD
  parent_id: null
  user_id: 7b12666ef7c7447196b7b2620975033b
  - trigger:
      - platform: "event"
        event_type: "android.intent_received"
        event_data:
          android.bluetooth.device.extra.DEVICE: "XX:2B:8C:C8:XX:XX"
          intent: "android.bluetooth.device.action.BATTERY_LEVEL_CHANGED"
  - sensor:
      - name: "Matthew's Buds2 Pro Battery Level"
        unique_id: "d8a50173-9383-4e9d-91ae-1fb26795942f"
        device_class: "battery"
        unit_of_measurement: "%"
        state_class: "measurement"
        availability: "{{ trigger.event['data']['android.bluetooth.device.extra.BATTERY_LEVEL'] != '-1' }}"
        state: "{{ trigger.event['data']['android.bluetooth.device.extra.BATTERY_LEVEL'] }}"

Have you received any events since setting up the template sensor? If not, the best way to trigger them is to turn the headphones on and off or connect and disconnect them.

Yes. I have received plenty. The template sensor never updates.

Just took a second look at your code, you have an extra -.

Should be:

  - trigger:
      - platform: "event"
        event_type: "android.intent_received"
        event_data:
          android.bluetooth.device.extra.DEVICE: "XX:2B:8C:C8:XX:XX"
          intent: "android.bluetooth.device.action.BATTERY_LEVEL_CHANGED"
    sensor:
      - name: "Matthew's Buds2 Pro Battery Level"
        unique_id: "d8a50173-9383-4e9d-91ae-1fb26795942f"
        device_class: "battery"
        unit_of_measurement: "%"
        state_class: "measurement"
        availability: "{{ trigger.event['data']['android.bluetooth.device.extra.BATTERY_LEVEL'] != '-1' }}"
        state: "{{ trigger.event['data']['android.bluetooth.device.extra.BATTERY_LEVEL'] }}"

Sorry I didn’t spot it sooner, just needed fresh eyes, I guess, because I spotted it instantly this time.

1 Like

Huh. Perfect! Thanks. All up and working…

1 Like

How do you make the battery level stay when you disconnect from the ear buds?

I have tried with

        state: >- 
                {%- if trigger.event['data']['android.bluetooth.device.extra.BATTERY_LEVEL'] | int >= 0 and trigger.event['data']['android.bluetooth.device.extra.BATTERY_LEVEL'] | int <= 100 -%}
                    {{ trigger.event['data']['android.bluetooth.device.extra.BATTERY_LEVEL'] }}
                {%- else -%}
                    {{ states('sensor.urbanista_battery_level') }}
                {%- endif -%}

And also with != "unknown" or != "unavailable" but state keeps being unavailable when I disconnect

To fully achieve this, it is necessary to use an input_number help (so that it persists over ha restarts).

I have an automation that is triggered on changes in the state of the template sensor, checks that the sensor is available and updates the input_number. Then I have a template sensor that copies the value of that input_number to make it show up as a battery.

This is the automation that I use, it will do as many batteries as you like:

alias: Bluetooth Battery Level (Last)
description: ""
trigger:
  - platform: state
    entity_id:
      - [[ list of sensors ]]
    not_to:
      - unknown
      - unavailable
condition: []
action:
  - service: input_number.set_value
    target:
      entity_id: input_number.{{ trigger.entity_id.split('.')[1] }}_last
    data:
      value: "{{ trigger.to_state.state }}"
mode: single

The input_number and template sensor then need to be setup via the helpers menu.

I don’t mind losing it because of a restart. It’s not that important.
I just want it to stay in between restarts.

I just tried this also, didn’t work

                {%- if trigger.event['data']['android.bluetooth.device.extra.BATTERY_LEVEL'] | is_number -%}
                    {{ trigger.event['data']['android.bluetooth.device.extra.BATTERY_LEVEL'] }}
                {%- else -%}
                    {{ states('sensor.urbanista_battery_level') }}
                {%- endif -%}

Makes you wonder what the vaule actually is.

Finally!

it’s the availability line that needs to be removed.

    sensor:
      - name: "Urbanista Battery Level"
        unique_id: "Urbanista battery"
        device_class: "battery"
        unit_of_measurement: "%"
        state_class: "measurement"
        #availability: "{{ trigger.event['data']['android.bluetooth.device.extra.BATTERY_LEVEL'] != '-1' }}"
        state: >- 
                {%- if trigger.event['data']['android.bluetooth.device.extra.BATTERY_LEVEL'] | int <= 0 -%}
                    {{ states('sensor.urbanista_battery_level') }}
                {%- else -%}
                    {{ trigger.event['data']['android.bluetooth.device.extra.BATTERY_LEVEL'] }}
                {%- endif -%}

I think so at least. Let’s see if the values update as they should also.

1 Like

It’s confirmed. Removing the availability line and adding the if else will retain the battery state when you disconnect.

1 Like

Here’s an updated take on configuring Bluetooth Battery Levels.

I wanted two sensors:

  • one to persist the last reported level
  • the other shows as unavailable when the bluetooth device is off

The state, including attributes, of trigger-based sensors and binary sensors is restored when Home Assistant is restarted.

So this is possible without the input_number workaround now.

Can make use of trigger variables in sensor templates too.

template:
  - trigger: 
      - platform: event
        event_type: android.intent_received
        event_data:
          android.bluetooth.device.extra.DEVICE: "<mac_address>"
          intent: android.bluetooth.device.action.BATTERY_LEVEL_CHANGED
        variables:
          battery_level: "{{ trigger.event.data['android.bluetooth.device.extra.BATTERY_LEVEL'] | int }}"
    sensor:
      - name: "<device_name> Battery Level"
        unique_id: "<uuid>"
        device_class: battery
        state_class: measurement
        unit_of_measurement: "%"
        state: "{{ battery_level }}"
        availability: "{{ battery_level >= 0 }}"

      - name: "<device_name> Battery Level (Last)"
        unique_id: "<uuid>"
        device_class: battery
        state_class: measurement
        unit_of_measurement: "%"
        state: "{{ battery_level if battery_level >= 0 else states(this.entity_id) }}"

Bonus - yaml anchors and merge keys to repeat for multiple devices…

Summary
template:
  - trigger: 
      - &battery-level-trigger
        platform: event
        event_type: android.intent_received
        event_data:
          android.bluetooth.device.extra.DEVICE: "<mac_address>"
          intent: android.bluetooth.device.action.BATTERY_LEVEL_CHANGED
        variables:
          battery_level: "{{ trigger.event.data['android.bluetooth.device.extra.BATTERY_LEVEL'] | int }}"
    sensor:
      - &battery-level-sensor
        name: "<device_name> Battery Level"
        unique_id: "<uuid>"
        device_class: battery
        state_class: measurement
        unit_of_measurement: "%"
        state: "{{ battery_level }}"
        availability: "{{ battery_level >= 0 }}"

      - &last-battery-level-sensor
        name: "<device_name> Battery Level (Last)"
        unique_id: "<uuid>"
        device_class: battery
        state_class: measurement
        unit_of_measurement: "%"
        state: "{{ battery_level if battery_level >= 0 else states(this.entity_id) }}"

  - trigger: 
      - <<: *battery-level-trigger
        event_data:
          android.bluetooth.device.extra.DEVICE: "<mac_address_2>"
          intent: android.bluetooth.device.action.BATTERY_LEVEL_CHANGED
    sensor:
      - <<: *battery-level-sensor
        name: "<device_name_2> Battery Level"
        unique_id: "<uuid>"
      - <<: *last-battery-level-sensor
        name: "<device_name_2> Battery Level (Last)"
        unique_id: "<uuid>"

  - trigger: 
      - <<: *battery-level-trigger
        event_data:
          android.bluetooth.device.extra.DEVICE: "<mac_address_3>"
          intent: android.bluetooth.device.action.BATTERY_LEVEL_CHANGED
    sensor:
      - <<: *battery-level-sensor
        name: "<device_name_3> Battery Level"
        unique_id: "<uuid>"
      - <<: *last-battery-level-sensor
        name: "<device_name_3> Battery Level (Last)"
        unique_id: "<uuid>"

image

This is nice work, my one note is that in my experience, the template sensors forget their values when Home-Assistant restarts and input_numbers do not, so while it is more annoying to setup it was necessary in my case. This solution should work apart from when you restart Home-Assistant, thank you for sharing.

Just wondering is there a Hacs integration for this BT monitoring?

There is not a HACS integration for this, as it uses the mobile app and template sensors (which are built in). There is no installation as such, it is just configuration, as is shown in the post above.