mirror of
https://github.com/home-assistant/core.git
synced 2025-07-19 11:17:21 +00:00
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:
parent
d7f736ed6c
commit
c8cce7607c
59
homeassistant/components/bluetooth_le_tracker/device_tracker.py
Normal file → Executable file
59
homeassistant/components/bluetooth_le_tracker/device_tracker.py
Normal file → Executable 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:
|
||||||
|
Loading…
x
Reference in New Issue
Block a user