Add battery information to BLE devices (#33222)

* Add battery information to BLE devices

* Check Bluetooth LE battery at most once a day
This commit is contained in:
guillempages 2020-05-09 22:18:35 +02:00 committed by GitHub
parent d7f736ed6c
commit c8cce7607c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

View File

@ -1,6 +1,8 @@
"""Tracking for bluetooth low energy devices.""" """Tracking for bluetooth low energy devices."""
import asyncio import asyncio
from datetime import datetime, timedelta
import logging import logging
from uuid import UUID
import pygatt # pylint: disable=import-error import pygatt # pylint: disable=import-error
@ -20,6 +22,11 @@ import homeassistant.util.dt as dt_util
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
# Base UUID: 00000000-0000-1000-8000-00805F9B34FB
# Battery characteristic: 0x2a19 (https://www.bluetooth.com/specifications/gatt/characteristics/)
BATTERY_CHARACTERISTIC_UUID = UUID("00002a19-0000-1000-8000-00805f9b34fb")
CONF_TRACK_BATTERY = "track_battery"
DEFAULT_TRACK_BATTERY_INTERVAL = timedelta(days=1)
DATA_BLE = "BLE" DATA_BLE = "BLE"
DATA_BLE_ADAPTER = "ADAPTER" DATA_BLE_ADAPTER = "ADAPTER"
BLE_PREFIX = "BLE_" BLE_PREFIX = "BLE_"
@ -42,7 +49,12 @@ def setup_scanner(hass, config, see, discovery_info=None):
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, handle_stop) hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, handle_stop)
def see_device(address, name, new_device=False): if config.get(CONF_TRACK_BATTERY):
battery_track_interval = DEFAULT_TRACK_BATTERY_INTERVAL
else:
battery_track_interval = timedelta(0)
def see_device(address, name, new_device=False, battery=None):
"""Mark a device as seen.""" """Mark a device as seen."""
if name is not None: if name is not None:
name = name.strip("\x00") name = name.strip("\x00")
@ -59,6 +71,10 @@ def setup_scanner(hass, config, see, discovery_info=None):
return return
_LOGGER.debug("Adding %s to tracked devices", address) _LOGGER.debug("Adding %s to tracked devices", address)
devs_to_track.append(address) devs_to_track.append(address)
if battery_track_interval > timedelta(0):
devs_track_battery[address] = dt_util.as_utc(
datetime.fromtimestamp(0)
)
else: else:
_LOGGER.debug("Seen %s for the first time", address) _LOGGER.debug("Seen %s for the first time", address)
new_devices[address] = {"seen": 1, "name": name} new_devices[address] = {"seen": 1, "name": name}
@ -68,6 +84,7 @@ def setup_scanner(hass, config, see, discovery_info=None):
mac=BLE_PREFIX + address, mac=BLE_PREFIX + address,
host_name=name, host_name=name,
source_type=SOURCE_TYPE_BLUETOOTH_LE, source_type=SOURCE_TYPE_BLUETOOTH_LE,
battery=battery,
) )
def discover_ble_devices(): def discover_ble_devices():
@ -88,6 +105,7 @@ def setup_scanner(hass, config, see, discovery_info=None):
yaml_path = hass.config.path(YAML_DEVICES) yaml_path = hass.config.path(YAML_DEVICES)
devs_to_track = [] devs_to_track = []
devs_donot_track = [] devs_donot_track = []
devs_track_battery = {}
# Load all known devices. # Load all known devices.
# We just need the devices so set consider_home and home range # We just need the devices so set consider_home and home range
@ -97,12 +115,17 @@ def setup_scanner(hass, config, see, discovery_info=None):
).result(): ).result():
# check if device is a valid bluetooth device # check if device is a valid bluetooth device
if device.mac and device.mac[:4].upper() == BLE_PREFIX: if device.mac and device.mac[:4].upper() == BLE_PREFIX:
address = device.mac[4:]
if device.track: if device.track:
_LOGGER.debug("Adding %s to BLE tracker", device.mac) _LOGGER.debug("Adding %s to BLE tracker", device.mac)
devs_to_track.append(device.mac[4:]) devs_to_track.append(address)
if battery_track_interval > timedelta(0):
devs_track_battery[address] = dt_util.as_utc(
datetime.fromtimestamp(0)
)
else: else:
_LOGGER.debug("Adding %s to BLE do not track", device.mac) _LOGGER.debug("Adding %s to BLE do not track", device.mac)
devs_donot_track.append(device.mac[4:]) devs_donot_track.append(address)
# if track new devices is true discover new devices # if track new devices is true discover new devices
# on every scan. # on every scan.
@ -117,13 +140,41 @@ def setup_scanner(hass, config, see, discovery_info=None):
def update_ble(now): def update_ble(now):
"""Lookup Bluetooth LE devices and update status.""" """Lookup Bluetooth LE devices and update status."""
devs = discover_ble_devices() devs = discover_ble_devices()
if devs_track_battery:
adapter = hass.data[DATA_BLE][DATA_BLE_ADAPTER]
for mac in devs_to_track: for mac in devs_to_track:
if mac not in devs: if mac not in devs:
continue continue
if devs[mac] is None: if devs[mac] is None:
devs[mac] = mac devs[mac] = mac
see_device(mac, devs[mac])
battery = None
if (
mac in devs_track_battery
and now > devs_track_battery[mac] + battery_track_interval
):
handle = None
try:
adapter.start(reset_on_start=True)
_LOGGER.debug("Reading battery for Bluetooth LE device %s", mac)
bt_device = adapter.connect(mac)
# Try to get the handle; it will raise a BLEError exception if not available
handle = bt_device.get_handle(BATTERY_CHARACTERISTIC_UUID)
battery = ord(bt_device.char_read(BATTERY_CHARACTERISTIC_UUID))
devs_track_battery[mac] = now
except pygatt.exceptions.NotificationTimeout:
_LOGGER.warning("Timeout when trying to get battery status")
except pygatt.exceptions.BLEError as err:
_LOGGER.warning("Could not read battery status: %s", err)
if handle is not None:
# If the device does not offer battery information, there is no point in asking again later on.
# Remove the device from the battery-tracked devices, so that their battery is not wasted
# trying to get an unavailable information.
del devs_track_battery[mac]
finally:
adapter.stop()
see_device(mac, devs[mac], battery=battery)
if track_new: if track_new:
for address in devs: for address in devs: