Skip to content

Commit

Permalink
Code styling tweaks to Bluetooth (home-assistant#85448)
Browse files Browse the repository at this point in the history
Co-authored-by: J. Nick Koston <[email protected]>
  • Loading branch information
frenck and bdraco authored Jan 8, 2023
1 parent 800b8ab commit 487782a
Show file tree
Hide file tree
Showing 8 changed files with 87 additions and 50 deletions.
20 changes: 13 additions & 7 deletions homeassistant/components/bluetooth/active_update_coordinator.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
"""A Bluetooth passive coordinator that receives data from advertisements but can also poll."""
"""A Bluetooth passive coordinator.
Receives data from advertisements but can also poll.
"""
from __future__ import annotations

from collections.abc import Callable, Coroutine
Expand Down Expand Up @@ -33,16 +36,19 @@ class ActiveBluetoothDataUpdateCoordinator(
out if a poll is needed. This should return True if it is and False if it is
not needed.
def needs_poll_method(svc_info: BluetoothServiceInfoBleak, last_poll: float | None) -> bool:
def needs_poll_method(
svc_info: BluetoothServiceInfoBleak,
last_poll: float | None
) -> bool:
return True
If there has been no poll since HA started, `last_poll` will be None. Otherwise it is
the number of seconds since one was last attempted.
If there has been no poll since HA started, `last_poll` will be None.
Otherwise it is the number of seconds since one was last attempted.
If a poll is needed, the coordinator will call poll_method. This is a coroutine.
It should return the same type of data as your update_method. The expectation is that
data from advertisements and from polling are being parsed and fed into a shared
object that represents the current state of the device.
It should return the same type of data as your update_method. The expectation is
that data from advertisements and from polling are being parsed and fed into
a shared object that represents the current state of the device.
async def poll_method(svc_info: BluetoothServiceInfoBleak) -> YourDataType:
return YourDataType(....)
Expand Down
25 changes: 16 additions & 9 deletions homeassistant/components/bluetooth/active_update_processor.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
"""A Bluetooth passive processor coordinator that collects data from advertisements but can also poll."""
"""A Bluetooth passive processor coordinator.
Collects data from advertisements but can also poll.
"""
from __future__ import annotations

from collections.abc import Callable, Coroutine
Expand All @@ -23,23 +26,27 @@
class ActiveBluetoothProcessorCoordinator(
Generic[_T], PassiveBluetoothProcessorCoordinator[_T]
):
"""
A processor coordinator that parses passive data from advertisements but can also poll.
"""A processor coordinator that parses passive data.
Parses passive data from advertisements but can also poll.
Every time an advertisement is received, needs_poll_method is called to work
out if a poll is needed. This should return True if it is and False if it is
not needed.
def needs_poll_method(svc_info: BluetoothServiceInfoBleak, last_poll: float | None) -> bool:
def needs_poll_method(
svc_info: BluetoothServiceInfoBleak,
last_poll: float | None
) -> bool:
return True
If there has been no poll since HA started, `last_poll` will be None. Otherwise it is
the number of seconds since one was last attempted.
If there has been no poll since HA started, `last_poll` will be None.
Otherwise it is the number of seconds since one was last attempted.
If a poll is needed, the coordinator will call poll_method. This is a coroutine.
It should return the same type of data as your update_method. The expectation is that
data from advertisements and from polling are being parsed and fed into a shared
object that represents the current state of the device.
It should return the same type of data as your update_method. The expectation is
that data from advertisements and from polling are being parsed and fed into a
shared object that represents the current state of the device.
async def poll_method(svc_info: BluetoothServiceInfoBleak) -> YourDataType:
return YourDataType(....)
Expand Down
9 changes: 6 additions & 3 deletions homeassistant/components/bluetooth/base_scanner.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,8 @@ def _async_watchdog_triggered(self) -> bool:
def _async_scanner_watchdog(self, now: datetime.datetime) -> None:
"""Check if the scanner is running.
Override this method if you need to do something else when the watchdog is triggered.
Override this method if you need to do something else when the watchdog
is triggered.
"""
if self._async_watchdog_triggered():
_LOGGER.info(
Expand Down Expand Up @@ -144,6 +145,7 @@ def discovered_devices_and_advertisement_data(

async def async_diagnostics(self) -> dict[str, Any]:
"""Return diagnostic information about the scanner."""
device_adv_datas = self.discovered_devices_and_advertisement_data.values()
return {
"name": self.name,
"start_time": self._start_time,
Expand All @@ -160,7 +162,7 @@ async def async_diagnostics(self) -> dict[str, Any]:
"advertisement_data": device_adv[1],
"details": device_adv[0].details,
}
for device_adv in self.discovered_devices_and_advertisement_data.values()
for device_adv in device_adv_datas
],
}

Expand Down Expand Up @@ -258,9 +260,10 @@ def _async_expire_devices(self, _datetime: datetime.datetime) -> None:
@property
def discovered_devices(self) -> list[BLEDevice]:
"""Return a list of discovered devices."""
device_adv_datas = self._discovered_device_advertisement_datas.values()
return [
device_advertisement_data[0]
for device_advertisement_data in self._discovered_device_advertisement_datas.values()
for device_advertisement_data in device_adv_datas
]

@property
Expand Down
37 changes: 21 additions & 16 deletions homeassistant/components/bluetooth/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -225,15 +225,17 @@ def async_get_scanner_discovered_devices_and_advertisement_data_by_address(
results: list[tuple[BaseHaScanner, BLEDevice, AdvertisementData]] = []
for type_ in types_:
for scanner in self._get_scanners_by_type(type_):
if device_advertisement_data := scanner.discovered_devices_and_advertisement_data.get(
address
):
results.append((scanner, *device_advertisement_data))
devices_and_adv_data = scanner.discovered_devices_and_advertisement_data
if device_adv_data := devices_and_adv_data.get(address):
results.append((scanner, *device_adv_data))
return results

@hass_callback
def _async_all_discovered_addresses(self, connectable: bool) -> Iterable[str]:
"""Return all of discovered addresses from all the scanners including duplicates."""
"""Return all of discovered addresses.
Include addresses from all the scanners including duplicates.
"""
yield from itertools.chain.from_iterable(
scanner.discovered_devices_and_advertisement_data
for scanner in self._get_scanners_by_type(True)
Expand Down Expand Up @@ -281,9 +283,9 @@ def _async_check_unavailable(self, now: datetime) -> None:
#
# For non-connectable devices we also check the device has exceeded
# the advertising interval before we mark it as unavailable
# since it may have gone to sleep and since we do not need an active connection
# to it we can only determine its availability by the lack of advertisements
#
# since it may have gone to sleep and since we do not need an active
# connection to it we can only determine its availability
# by the lack of advertisements
if advertising_interval := intervals.get(address):
time_since_seen = monotonic_now - all_history[address].time
if time_since_seen <= advertising_interval:
Expand Down Expand Up @@ -335,7 +337,8 @@ def _prefer_previous_adv_from_different_source(
if (new.rssi or NO_RSSI_VALUE) - RSSI_SWITCH_THRESHOLD > (
old.rssi or NO_RSSI_VALUE
):
# If new advertisement is RSSI_SWITCH_THRESHOLD more, the new one is preferred
# If new advertisement is RSSI_SWITCH_THRESHOLD more,
# the new one is preferred.
if debug:
_LOGGER.debug(
(
Expand Down Expand Up @@ -381,19 +384,21 @@ def scanner_adv_received(self, service_info: BluetoothServiceInfoBleak) -> None:

source = service_info.source
debug = _LOGGER.isEnabledFor(logging.DEBUG)
# This logic is complex due to the many combinations of scanners that are supported.
# This logic is complex due to the many combinations of scanners
# that are supported.
#
# We need to handle multiple connectable and non-connectable scanners
# and we need to handle the case where a device is connectable on one scanner
# but not on another.
#
# The device may also be connectable only by a scanner that has worse signal strength
# than a non-connectable scanner.
# The device may also be connectable only by a scanner that has worse
# signal strength than a non-connectable scanner.
#
# all_history - the history of all advertisements from all scanners with the best
# advertisement from each scanner
# connectable_history - the history of all connectable advertisements from all scanners
# with the best advertisement from each connectable scanner
# all_history - the history of all advertisements from all scanners with the
# best advertisement from each scanner
# connectable_history - the history of all connectable advertisements from all
# scanners with the best advertisement from each
# connectable scanner
#
if (
(old_service_info := all_history.get(address))
Expand Down
5 changes: 4 additions & 1 deletion homeassistant/components/bluetooth/match.py
Original file line number Diff line number Diff line change
Expand Up @@ -282,7 +282,10 @@ class BluetoothMatcherIndex(BluetoothMatcherIndexBase[BluetoothMatcher]):
class BluetoothCallbackMatcherIndex(
BluetoothMatcherIndexBase[BluetoothCallbackMatcherWithCallback]
):
"""Bluetooth matcher for the bluetooth integration that supports matching on addresses."""
"""Bluetooth matcher for the bluetooth integration.
Supports matching on addresses.
"""

def __init__(self) -> None:
"""Initialize the matcher index."""
Expand Down
11 changes: 8 additions & 3 deletions homeassistant/components/bluetooth/usage.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,22 @@


def install_multiple_bleak_catcher() -> None:
"""Wrap the bleak classes to return the shared instance if multiple instances are detected."""
"""Wrap the bleak classes to return the shared instance.
In case multiple instances are detected.
"""
bleak.BleakScanner = HaBleakScannerWrapper # type: ignore[misc, assignment]
bleak.BleakClient = HaBleakClientWrapper # type: ignore[misc]
bleak_retry_connector.BleakClientWithServiceCache = HaBleakClientWithServiceCache # type: ignore[misc,assignment]
bleak_retry_connector.BleakClientWithServiceCache = HaBleakClientWithServiceCache # type: ignore[misc,assignment] # noqa: E501


def uninstall_multiple_bleak_catcher() -> None:
"""Unwrap the bleak classes."""
bleak.BleakScanner = ORIGINAL_BLEAK_SCANNER # type: ignore[misc]
bleak.BleakClient = ORIGINAL_BLEAK_CLIENT # type: ignore[misc]
bleak_retry_connector.BleakClientWithServiceCache = ORIGINAL_BLEAK_RETRY_CONNECTOR_CLIENT # type: ignore[misc]
bleak_retry_connector.BleakClientWithServiceCache = ( # type: ignore[misc]
ORIGINAL_BLEAK_RETRY_CONNECTOR_CLIENT
)


class HaBleakClientWithServiceCache(HaBleakClientWrapper):
Expand Down
5 changes: 4 additions & 1 deletion homeassistant/components/bluetooth/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,10 @@
def async_load_history_from_system(
adapters: BluetoothAdapters, storage: BluetoothStorage
) -> tuple[dict[str, BluetoothServiceInfoBleak], dict[str, BluetoothServiceInfoBleak]]:
"""Load the device and advertisement_data history if available on the current system."""
"""Load the device and advertisement_data history.
Only loads if available on the current system.
"""
now_monotonic = monotonic_time_coarse()
connectable_loaded_history: dict[str, BluetoothServiceInfoBleak] = {}
all_loaded_history: dict[str, BluetoothServiceInfoBleak] = {}
Expand Down
25 changes: 15 additions & 10 deletions homeassistant/components/bluetooth/wrappers.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,10 +119,11 @@ def discovered_devices(self) -> list[BLEDevice]:
def register_detection_callback(
self, callback: AdvertisementDataCallback | None
) -> None:
"""Register a callback that is called when a device is discovered or has a property changed.
"""Register a detection callback.
This method takes the callback and registers it with the long running
scanner.
The callback is called when a device is discovered or has a property changed.
This method takes the callback and registers it with the long running sscanner.
"""
self._advertisement_data_callback = callback
self._setup_detection_callback()
Expand Down Expand Up @@ -154,7 +155,9 @@ def _rssi_sorter_with_connection_failure_penalty(
connection_failure_count: dict[BaseHaScanner, int],
rssi_diff: int,
) -> float:
"""Get a sorted list of scanner, device, advertisement data adjusting for previous connection failures.
"""Get a sorted list of scanner, device, advertisement data.
Adjusting for previous connection failures.
When a connection fails, we want to try the next best adapter so we
apply a penalty to the RSSI value to make it less likely to be chosen
Expand Down Expand Up @@ -227,7 +230,10 @@ def set_disconnected_callback(
"""Set the disconnect callback."""
self.__disconnected_callback = callback
if self._backend:
self._backend.set_disconnected_callback(callback, **kwargs) # type: ignore[arg-type]
self._backend.set_disconnected_callback(
callback, # type: ignore[arg-type]
**kwargs,
)

async def connect(self, **kwargs: Any) -> bool:
"""Connect to the specified GATT server."""
Expand Down Expand Up @@ -294,15 +300,14 @@ def _async_get_best_available_backend_and_device(
that has a free connection slot.
"""
address = self.__address
scanner_device_advertisement_datas = manager.async_get_scanner_discovered_devices_and_advertisement_data_by_address(
scanner_device_advertisement_datas = manager.async_get_scanner_discovered_devices_and_advertisement_data_by_address( # noqa: E501
address, True
)
sorted_scanner_device_advertisement_datas = sorted(
scanner_device_advertisement_datas,
key=lambda scanner_device_advertisement_data: scanner_device_advertisement_data[
2
].rssi
or NO_RSSI_VALUE,
key=lambda scanner_device_advertisement_data: (
scanner_device_advertisement_data[2].rssi or NO_RSSI_VALUE
),
reverse=True,
)

Expand Down

0 comments on commit 487782a

Please sign in to comment.