Monitoring ANT+ / BLE Battery Levels

Is anyone out there getting the battery levels from BLE Devices (e.g. Bike Lights, fitness tracking watches and possibly other bike sensors).

Using an Android App shows that on my bike light and smart watch the values are readable:-

or

A blog post https://www.fortinet.com/blog/threat-research/reading-your-tracker-s-battery-level-with-a-standard-bluetooth-4-0-usb-dongle.html and github repo https://github.com/cryptax/fittools hint they are readable - although python and gattlib on a pi seems to be a pain to get working (I’ve not succeed yet, and the BLE devices are currently in use anyway).

The first issue I see is that my bike sensors (currently only a light and an old ANT+ speed sensor) aren’t visible for long - the bluetooth_le device tracker sees it for about 5 minutes after I get home (assuming the bluetooth_le tracker hasn’t fallen over) and the second one is I wouldn’t want constant polling as it would flatten the battery - however I foresee an issue in doing an automation when the device becomes visible - chances are all the ble_sensors would go from Away to Home about the same time and you’d end up with multiple shell_scripts trying to use the same ble dongle at the same time.

Being able to know / reminded my bike lights are below 25% would be rather handy! One device I have is BLE only, one is ANT+ only (although that is planned for replacement), the rest I have/am planning on getting are BLE and ANT+ so BLE is probably the better path to go down, but the constant streaming to multiple devices of ANT+ sounds like it could be more reliable?

I’ve got something working, it ain’t pretty but does the job…

The key bit is realising that you can read the battery level (in Hex) using the following command

gatttool -b {$mac} --char-read --uuid=0x2A19 -t random

From that Acron does grow a bowl of spaghetti…

In configuration.yaml add a new rest_command:

rest_command:
  bike_light_home:
    url: "http://pihole.local/battery.php?mac=AA:AA:AA:AA:AA:AA&name=Rear%20Light"

The add to automations.yaml a rule to call this when the bike gets home.

- id: bike_light_home
  alias: Rear Bike Light has Arrived Home
  trigger:
    platform: state
    entity_id: device_tracker.rear_bike_light
    to: 'home'
  action:
    - service: rest_command.bike_light_home

Then on my UniFi Controller Machine (which is also running PiHole) /var/www/html/battery.php (needed to install php-curl too) - The reason It’s on a separate machine is because that Bluetooth adapter is free, the Hass Machines Bluetooth Adaptors being used for Bluetooth and Bluetooth LE Presence detection - has the advantage of the UniFi controller being nearer to where the bikes are stored too and therefore a more reliable signal - you could probably install another adaptor on your Hass machine to achieve the same)

<?php

$info = array("mac"=>$_GET['mac'],"name"=>$_GET['name']);

file_put_contents("/tmp/ble_" . str_replace(":","",$_GET['mac']) . ".ble",json_encode($info));
echo $_GET['mac'];
?>

Then in the crontab call a PHP script every minute, to look for these temp files and query the lights in turn…

<?php
require("RemoteFiles.class.php");
$dir    = '/tmp';
$files = scandir($dir);
$macs = array();
foreach($files as $file)
{
	$fn = "{$dir}/{$file}";
	if (substr($file,-4)==".ble")
	{
		$j = json_decode(file_get_contents($fn));
		unlink($fn);
		$macs[$j->mac] = $j->name;
	}	
}

foreach($macs as $mac=>$label)
{
	for($i = 0; $i <= 10; $i++)
	{
		$out = shell_exec("gatttool -b {$mac} --char-read --uuid=0x2A19 -t random");
		if(strpos($out,"handle")!==false)
		{
			$out = preg_replace('!\s+!', ' ', $out);
			$out = explode(" ",$out);
			if($out[2]=='value:')
			{
				$sensor = "ble_battery_" . str_replace(":","",$mac);
				echo hexdec($out[3]);
				update_hass_status($sensor,$label . " Battery",hexdec($out[3]),"%");
			}
			$i =11;
		}
	}
}

function update_hass_status($sensor,$name,$value,$unit) 
{
	$sensor = trim(strtolower($sensor));
	$value = (string)$value;
	$data = json_encode(array('state'=>$value,'attributes'=>array("unit_of_measurement"=> $unit, "friendly_name"=> $name . " at " . date('H:i d-M'))));
	file_put_contents("/tmp/btlelog_{$sensor}.btle",$data);
	$x = RemoteFiles::Post("http://hass.local:8123/api/states/sensor.{$sensor}",$data,array("Content-Type: application/json"));
}

Then when I get home…

  • The Bluetooth LE Scanner changes the status of the Bike Light to home
  • Which triggers automation.bike_light_home
  • Which calls rest_command.bike_light_home
  • Which calls http://pihole.local/battery.php with the Mac Address and a Friendly Name
  • Which writes a tmp file containing those details
  • Within 1 minute Cron calls a PHP Script which reads those temporary files, attempts to read the battery level over BLE, and if successful calls a HTTP Sensor endpoint on Hass
  • Which processes that HTTP Call and therefore updates the battery level
  • Which then becomes visible on my Lovelace Cycling Dashboard

And rather handily moving it to put it on charge or take it off charge causes the presence to switch to home, updating the details

(I’m adding the date/time to the description so I can easily see the age)

And that’s how I can monitor the battery level of my new bike light before the Android app has even been released. And is now annoying me that I can’t see the battery level of my dumb lights :disappointed_relieved:

Now to work out how to tidy the automations so I don’t need a separate automation and rest command for each device - must be a way of grouping them with the name/mac being options

1 Like

@kevjs1982

This is excellent! Exactly what I’m looking for.

I did try it with a BLE battery monitor for a car battery but get this…it doesn’t show the battery level in the available attributes! It’s a battery monitor, but you can’t actually read the battery level it’s reporting back to a Battery Monitor app anyway.

Still a great post though as I’ve been wondering how to do this so cheers!

1 Like

That’s quite neat! I am gonna have to check if I can do the same thing with my Xiaomi Scooter.
It would be quite neat to retrieve all the usage\information stats out of it and have automations to tell me when to recharge it, remind me to give maintenance every few hundred km, etc.

I know this can done thru one of the android apps just by using BLE so it can definitely be done.