Ricardo
Ricardo

Reputation: 1275

Check battery level of connected Bluetooth device on Linux

How can I check the battery level of a connected Bluetooth device? The device shows the battery level on Android, so I'm assuming the device supports the GATT-based Battery Service.

However, by entering "menu gatt" in bluetoothctl and then listing the GATT attributes of the device with "list-attributes [dev]", nothing shows up.

There is a similar Stack Overflow question, How to check the battery level of a Bluetooth paired remote (now deleted, only visible to users with more than 10,000 reputation points), but the OP seems to have found a solution that doesn't work for me. When I run "info [dev]" in bluetoothctl, I don't see the UUID for 'Battery Service'.

I would prefer a solution that runs on the command line and is Linux distribution-agnostic.

Upvotes: 112

Views: 114877

Answers (16)

André M. Faria
André M. Faria

Reputation: 2162

One way to check the battery for my JBL headset was to issue the command below (command issued in Ubuntu 22.04 (Jammy Jellyfish)):

pactl link sink

This will show the following, in my case:

Sink #2
    State: RUNNING
    Name: bluez_sink.<MAC>.a2dp_sink
    Description: JBL TUNE710BT
    Driver: module-bluez5-device.c
    Sample Specification: s16le 2ch 44100Hz
    Channel Map: front-left,front-right
    Owner Module: 24
    Mute: no
    Volume: front-left: 32660 /  50% / -18,15 dB,   front-right: 32660 /  50% / -18,15 dB
            balance 0,00
    Base Volume: 65536 / 100% / 0,00 dB
    Monitor Source: bluez_sink.<MAC>.a2dp_sink.monitor
    Latency: 56606 usec, configured 42414 usec
    Flags: HARDWARE HW_VOLUME_CTRL DECIBEL_VOLUME LATENCY
    Properties:
        bluetooth.protocol = "a2dp_sink"
        bluetooth.codec = "sbc"
        device.description = "JBL TUNE710BT"
        device.string = "<MAC>"
        device.api = "bluez"
        device.class = "sound"
        device.bus = "bluetooth"
        device.form_factor = "headset"
        bluez.path = "/org/bluez/hci0/dev_<MAC>"
        bluez.class = "0x240404"
        bluez.alias = "JBL TUNE710BT"
        bluetooth.battery = "100%"
        device.icon_name = "audio-headset-bluetooth"
        device.intended_roles = "phone"
    Ports:
        headset-output: Headset (type: Headset, priority: 0, available)
    Active Port: headset-output
    Formats:
        pcm

As it’s possible to see, there exists a property called bluetooth.battery that shows the battery percentage.

Following the procedure stated in an answer to Check Bluetooth headphones battery status in Linux, I could show the battery percentage under SettingsPower and also using upower --dump.

Step by Step

  1. Add Experimental = True after [General] to file /etc/bluetooth/main.conf
  2. Restart the Bluetooth service: systemctl restart bluetooth
  3. Connect (or reconnect) you Bluetooth device
  4. Check the battery percentage at SettingsPower
sudo cp /etc/bluetooth/main.conf /etc/bluetooth/main.bkp
sed '/General]/a Experimental = True' /etc/bluetooth/main.conf | sudo tee /etc/bluetooth/main.conf
sudo systemctl restart bluetooth

But a warning!
After doing that, I can't use this Python script that is the center of Bluetooth Headset Battery Level or pactl list sinks any more.

Upvotes: 2

Vasily Olekhov
Vasily Olekhov

Reputation: 143

Here is a way to get battery level via PulseAudio logs with some hack.

My Bluetooth headset uses proprietary Apple HFP AT commands, and HFP/A2DP protocols are handled by PulseAudio directly. It seems the only way to get those values is through PulseAudio.

Upvotes: 5

Prime541
Prime541

Reputation: 31

There is a battery percentage in the info MAC command in bluetoothctl. The MAC addresses can be listed with the devices command in bluetoothctl.

bluetoothctl

Output:

hci0 new_settings: powered bondable ssp br/edr le secure-conn wide-band-speech cis-central cis-peripheral
[MX KEYS S]# Agent registered
[MX KEYS S]# [CHG] Controller C4:75:AB:8D:02:47 Pairable: yes

[MX KEYS S]# devices
Device DB:C0:CE:28:AD:06 MX KEYS S
Device AC:80:0A:2E:D3:35 WH-1000XM4
Device F8:8D:63:62:DE:07 MX Vertical

[MX KEYS S]# info DB:C0:CE:28:AD:06
Device DB:C0:CE:28:AD:06 (random)
    Name: MX KEYS S
    Alias: MX KEYS S
    Appearance: 0x03c1 (961)
    Icon: input-keyboard
    Paired: yes
    Bonded: yes
    Trusted: yes
    Blocked: no
    Connected: yes
    LegacyPairing: no
    UUID: Generic Access Profile    (00001800-0000-1000-8000-00805f9b34fb)
    UUID: Generic Attribute Profile (00001801-0000-1000-8000-00805f9b34fb)
    UUID: Device Information        (0000180a-0000-1000-8000-00805f9b34fb)
    UUID: Battery Service           (0000180f-0000-1000-8000-00805f9b34fb)
    UUID: Human Interface Device    (00001812-0000-1000-8000-00805f9b34fb)
    UUID: Logitech International SA (0000fd72-0000-1000-8000-00805f9b34fb)
    UUID: Vendor specific           (00010000-0000-1000-8000-011f2000046d)
    Modalias: usb:v046DpB378d0013
    Battery Percentage: 0x5a (90)

With little shell script:

echo 'devices' | bluetoothctl | grep '^Device ' | while read -r dev
do
    dev_mac=$(echo "$dev" | cut -d' ' -f2)
    dev_name=$(echo "$dev" | cut -d' ' -f3-)
    dev_battery=$(echo "info $dev_mac" | bluetoothctl | sed -n '/Battery Percentage:/ s/.*(\([0-9]*\).*/\1/p')
    echo "$dev_name: $dev_battery%"
done
MX KEYS S: 90%
WH-1000XM4: 50%
MX Vertical: 100%

Upvotes: 1

LHLaurini
LHLaurini

Reputation: 2570

(This answer is specific to headphones/headsets)

I'd been using the Python program from clst's answer for some time and although it worked, it required me to connect, then disconnect and run it again. If I understand the problem correctly, that happens because only one program can open a socket to talk to the Bluetooth device, so it ends up fighting with PulseAudio over it.

I've recently found out about hsphfpd.

hsphfpd is specification with some prototype implementation used for connecting Bluetooth devices with HSP and HFP profiles on Linux operating system.

Basically, since only one program can communicate with the headset at once and it wouldn't make sense to implement battery level reporting in an audio server, nor implement audio in a power management software, it moves that functionality to an external daemon. That way, PulseAudio and whatever can both use the headset at the same time. There is a version of PulseAudio patched to use hsphfpd. Even though these are both still prototypes, they seem to work very well.

hsphfpd reports battery status (and other stuff) through D-Bus, so to get it from the command line, you can just do

dbus-send --system --dest=org.hsphfpd --print-reply /org/hsphfpd/hci0/dev_XX_XX_XX_XX_XX_XX/hsp_hs org.freedesktop.DBus.Properties.Get string:org.hsphfpd.Endpoint string:BatteryLevel

or even call it from a program.

Both of these are available in the AUR, if you use Arch Linux.

Upvotes: 2

farfad
farfad

Reputation: 1

Install the extension for GNOME and "Bluetooth battery indicator". It works perfectly.

Upvotes: 0

VeRo
VeRo

Reputation: 1111

Am posting this today, because today I have tested this method successfully.

I received a tip from @Mr.Newbie, a Python solution: Bluetooth Headset Battery Level by @TheWeirdDev:

bluetoothctl devices # To identify the device and get its MAC address
                     # (for use with bluetooth_battery)
bluetooth_battery <device MAC address>

Result:

Enter image description here

Upvotes: 0

kooskoos
kooskoos

Reputation: 4879

On Ubuntu 20.04 (Focal Fossa) or later, it shows the battery under the 'Devices' tab in the power panel:

Enter image description here

Upvotes: 7

clst
clst

Reputation: 496

For me, this Python project has worked fine:

Bluetooth Headset Battery Level

I only had to change the port in line 57 to 3 for my no-name X5 headset. If it hangs or errors with "connection refused", try a different port.

The Python program uses AT commands via RFCOMM and should work while PulseAudio is using the A2DP sink (mine reconnects). Python 3 is needed as 2 doesn't have BT-Serial sockets. Windows will probably not work as it lacks BlueZ. It basically does the same thing as the PulseAudio hack at Check battery level of connected Bluetooth device on Linux.

If you want to look at the commands as they are exchanged, try my debug fork of Bluetooth Headset Battery Level.

Upvotes: 38

Martijn van Welie
Martijn van Welie

Reputation: 754

By default, BlueZ 'hides' the Battery Service UUID. This is because there is a 'battery plugin' loaded at startup of bluetoothd.

If you don't want the battery plugin to be activated and make the Battery Service UUID visible again to bluetoothctl or any other application, then change the startup command for bluetoothd to be like this: 'bluetoothd -P battery'. That will make sure the battery plugin is not loaded. On a Raspberry Pi the bluetooth.service is located in /lib/systemd/system/bluetooth.service so you need to make the change in that file.

Upvotes: 4

br1
br1

Reputation: 443

As said by OlivierM, the UUID is filtered by bluetoothd. You could undo that and export the UUID just as any other service characteristics by removing the following from the export_service() function in src/gatt-client.c

if (gatt_db_service_get_claimed(attr))
     return;

Upvotes: -4

VeRo
VeRo

Reputation: 1111

This is such a great question, ahead of development and tools that are available at the moment.

The short answer (in October 2018)

you have to write it yourself! It won't be a one liner in the terminal. I am going to write this for myself in Python, but C has a little more documentation, so if you are skilled with C go for it.

The long answer, but it's more a recommended starting point:

  1. Tony D: Raspberry Pi and Bluetooth LE, part 1 with Tony D (1 h 17 min 24 secs) managed to use bluetoothctl to read attributes and send data to a Bluetooth device. Definitely check the video information; you will find great links and references in Introduction to Bluetooth Low Energy.

  2. Szymon Janc: Doing Bluetooth Low Energy on Linux developer and contributor to the Linux Bluetooth Stack

  3. Definitely check out how this question is answered on mobile devices. For Android, it's the BAS (Battery Service): Displaying Bluetooth gadget's battery status on the phone

    On Android 8.0.1

Upvotes: 22

OlivierM
OlivierM

Reputation: 3212

You don't see 'Battery Level' in the list of GATT characteristics since BlueZ v5.48, because this specific GATT characteristic was moved into the D-Bus org.bluez.Battery1 interface.

From the command line:

  1. Connect to your target BLE device with bluetoothctl
  2. And then request D-Bus by running: dbus-send --print-reply=literal --system --dest=org.bluez /org/bluez/hci0/dev_<mac_address_of_your_ble_peripheral> org.freedesktop.DBus.Properties.Get string:"org.bluez.Battery1" string:"Percentage"

In my case, with my BLE peripheral with the following MAC address C3:41:A6:C8:93:42:

dbus-send --print-reply=literal --system --dest=org.bluez \
    /org/bluez/hci0/dev_C3_41_A6_C8_93_42 org.freedesktop.DBus.Properties.Get \
    string:"org.bluez.Battery1" string:"Percentage"

Output:

   variant       byte 94

Note: You could potentially scan and connect to your device using the BlueZ D-Bus API.

Upvotes: 29

Jayanth Rajan
Jayanth Rajan

Reputation: 29

In the BlueZ version, you are using the GATT attributes, and they may be experimental. If so, you need to enable the experimental characteristics by running the bluetoothd deamon by the -E keyword.

Like "/usr/libexec/bluetooth/bluetoothd -E".

This worked for me.

Upvotes: 2

baldrianbandit
baldrianbandit

Reputation: 21

Run bluetoothctl info while connected:

bluetoothctl info                                                                                                                
Device A0:E9:DB:04:49:81 (public)
    Name: Anker SoundCore
    Icon: audio-headset
    Paired: yes
    Connected: yes
    [...]
    Battery Percentage: 0x64 (100)

Tested with bluez-5.77.

Upvotes: 2

user202729
user202729

Reputation: 3985

One possible way (although hacky) is to grep inside the journal:

journalctl -b --user-unit pulseaudio -g "Battery Level" -o cat 

(-b to only show information in the current boot. Clearly information in the previous boots are irrelevant)

On my machine this outputs for example

Battery Level: 70%
Battery Level: 70%
Battery Level: 70%
Battery Level: 70%
Battery Level: 70%
Battery Level: 60%
Battery Level: 60%

Pick the last line (which can be done by appending |tail -n 1).

This solution does not distinguish whether the Bluetooth device is currently connected, or which one is in case there are more than one.


Alternative includes running bluetoothctl disconnect, then use the script in the accepted answer https://stackoverflow.com/a/59709851/5267751 to get the battery level, then run bluetoothctl connect again. This is usually not acceptable if you're currently connected, but it works.


Another alternative, if it works for you, is to use an experimental feature of bluez package. Refer to https://askubuntu.com/a/1420501/996767.

Upvotes: 10

Yash Nahata
Yash Nahata

Reputation: 577

For me running this in terminal worked:

upower --dump

Upvotes: 46

Related Questions