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
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