1 """Tracking for bluetooth devices."""
3 from __future__
import annotations
6 from datetime
import datetime, timedelta
8 from typing
import Final
11 from bt_proximity
import BluetoothRSSI
12 import voluptuous
as vol
18 PLATFORM_SCHEMA
as DEVICE_TRACKER_PLATFORM_SCHEMA,
42 _LOGGER: Final = logging.getLogger(__name__)
44 PLATFORM_SCHEMA: Final = DEVICE_TRACKER_PLATFORM_SCHEMA.extend(
46 vol.Optional(CONF_TRACK_NEW): cv.boolean,
47 vol.Optional(CONF_REQUEST_RSSI): cv.boolean,
48 vol.Optional(CONF_DEVICE_ID, default=DEFAULT_DEVICE_ID): vol.All(
49 vol.Coerce(int), vol.Range(min=-1)
56 """Check whether a device is a bluetooth device by its mac."""
57 return device.mac
is not None and device.mac[:3].upper() == BT_PREFIX
61 """Discover Bluetooth devices."""
63 result = bluetooth.discover_devices(
72 _LOGGER.error(
"Couldn't discover bluetooth devices: %s", ex)
74 _LOGGER.debug(
"Bluetooth devices discovered = %d", len(result))
80 async_see: AsyncSeeCallback,
83 rssi: tuple[int] |
None =
None,
85 """Mark a device as seen."""
88 attributes[
"rssi"] = rssi
91 mac=f
"{BT_PREFIX}{mac}",
92 host_name=device_name,
93 attributes=attributes,
94 source_type=SourceType.BLUETOOTH,
99 """Load all known devices.
101 We just need the devices so set consider_home and home range to 0
103 yaml_path: str = hass.config.path(YAML_DEVICES)
108 devices_to_track: set[str] = {
110 for device
in bluetooth_devices
111 if device.track
and device.mac
is not None
113 devices_to_not_track: set[str] = {
115 for device
in bluetooth_devices
116 if not device.track
and device.mac
is not None
119 return devices_to_track, devices_to_not_track
123 """Lookup a Bluetooth device name."""
124 _LOGGER.debug(
"Scanning %s", mac)
125 return bluetooth.lookup_name(mac, timeout=5)
131 async_see: AsyncSeeCallback,
132 discovery_info: DiscoveryInfoType |
None =
None,
134 """Set up the Bluetooth Scanner."""
135 device_id: int = config[CONF_DEVICE_ID]
136 interval: timedelta = config.get(CONF_SCAN_INTERVAL, SCAN_INTERVAL)
137 request_rssi: bool = config.get(CONF_REQUEST_RSSI,
False)
138 update_bluetooth_lock = asyncio.Lock()
141 track_new: bool = config.get(CONF_TRACK_NEW, DEFAULT_TRACK_NEW)
142 _LOGGER.debug(
"Tracking new devices is set to %s", track_new)
146 if not devices_to_track
and not track_new:
147 _LOGGER.debug(
"No Bluetooth devices to track and not tracking new devices")
150 _LOGGER.debug(
"Detecting RSSI for devices")
152 async
def perform_bluetooth_update() -> None:
153 """Discover Bluetooth devices and update status."""
154 _LOGGER.debug(
"Performing Bluetooth devices discovery and update")
155 tasks: list[asyncio.Task[
None]] = []
159 devices = await hass.async_add_executor_job(discover_devices, device_id)
160 for mac, _device_name
in devices:
161 if mac
not in devices_to_track
and mac
not in devices_to_not_track:
162 devices_to_track.add(mac)
164 for mac
in devices_to_track:
165 friendly_name = await hass.async_add_executor_job(lookup_name, mac)
166 if friendly_name
is None:
172 client = BluetoothRSSI(mac)
173 rssi = await hass.async_add_executor_job(client.request_rssi)
178 see_device(hass, async_see, mac, friendly_name, rssi)
183 await asyncio.wait(tasks)
185 except bluetooth.BluetoothError:
186 _LOGGER.exception(
"Error looking up Bluetooth device")
188 async
def update_bluetooth(now: datetime |
None =
None) ->
None:
189 """Lookup Bluetooth devices and update status."""
191 if update_bluetooth_lock.locked():
194 "Previous execution of update_bluetooth is taking longer than the"
195 " scheduled update of interval %s"
201 async
with update_bluetooth_lock:
202 await perform_bluetooth_update()
204 async
def handle_manual_update_bluetooth(call: ServiceCall) ->
None:
205 """Update bluetooth devices on demand."""
206 await update_bluetooth()
208 hass.async_create_task(update_bluetooth())
211 hass.services.async_register(DOMAIN, SERVICE_UPDATE, handle_manual_update_bluetooth)
bool is_bluetooth_device(Device device)
list[tuple[str, str]] discover_devices(int device_id)
bool async_setup_scanner(HomeAssistant hass, ConfigType config, AsyncSeeCallback async_see, DiscoveryInfoType|None discovery_info=None)
tuple[set[str], set[str]] get_tracking_devices(HomeAssistant hass)
None see_device(HomeAssistant hass, AsyncSeeCallback async_see, str mac, str device_name, tuple[int]|None rssi=None)
str|None lookup_name(str mac)
list[Device] async_load_config(str path, HomeAssistant hass, timedelta consider_home)
CALLBACK_TYPE async_track_time_interval(HomeAssistant hass, Callable[[datetime], Coroutine[Any, Any, None]|None] action, timedelta interval, *str|None name=None, bool|None cancel_on_shutdown=None)