From a641bbc352b7559c9491971fa6fc06437f4ab964 Mon Sep 17 00:00:00 2001 From: Jc2k Date: Mon, 5 Sep 2022 15:33:10 +0100 Subject: [PATCH] Less verbose error logs for bleak connection errors in ActiveBluetoothProcessorCoordinator (#77839) Co-authored-by: J. Nick Koston --- .../bluetooth/active_update_coordinator.py | 9 +++ .../test_active_update_coordinator.py | 76 +++++++++++++++++++ 2 files changed, 85 insertions(+) diff --git a/homeassistant/components/bluetooth/active_update_coordinator.py b/homeassistant/components/bluetooth/active_update_coordinator.py index e73414fe79f..b207f6fa2e1 100644 --- a/homeassistant/components/bluetooth/active_update_coordinator.py +++ b/homeassistant/components/bluetooth/active_update_coordinator.py @@ -6,6 +6,8 @@ import logging import time from typing import Any, Generic, TypeVar +from bleak import BleakError + from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.debounce import Debouncer @@ -109,6 +111,13 @@ class ActiveBluetoothProcessorCoordinator( try: update = await self._async_poll_data(self._last_service_info) + except BleakError as exc: + if self.last_poll_successful: + self.logger.error( + "%s: Bluetooth error whilst polling: %s", self.address, str(exc) + ) + self.last_poll_successful = False + return except Exception: # pylint: disable=broad-except if self.last_poll_successful: self.logger.exception("%s: Failure while polling", self.address) diff --git a/tests/components/bluetooth/test_active_update_coordinator.py b/tests/components/bluetooth/test_active_update_coordinator.py index 24ad96c523e..7677584e890 100644 --- a/tests/components/bluetooth/test_active_update_coordinator.py +++ b/tests/components/bluetooth/test_active_update_coordinator.py @@ -5,6 +5,8 @@ import asyncio import logging from unittest.mock import MagicMock, call, patch +from bleak import BleakError + from homeassistant.components.bluetooth import ( DOMAIN, BluetoothChange, @@ -162,6 +164,80 @@ async def test_poll_can_be_skipped(hass: HomeAssistant, mock_bleak_scanner_start cancel() +async def test_bleak_error_and_recover( + hass: HomeAssistant, mock_bleak_scanner_start, caplog +): + """Test bleak error handling and recovery.""" + await async_setup_component(hass, DOMAIN, {DOMAIN: {}}) + + flag = True + + def _update_method(service_info: BluetoothServiceInfoBleak): + return {"testdata": None} + + def _poll_needed(*args, **kwargs): + return True + + async def _poll(*args, **kwargs): + nonlocal flag + if flag: + raise BleakError("Connection was aborted") + return {"testdata": flag} + + coordinator = ActiveBluetoothProcessorCoordinator( + hass, + _LOGGER, + address="aa:bb:cc:dd:ee:ff", + mode=BluetoothScanningMode.ACTIVE, + update_method=_update_method, + needs_poll_method=_poll_needed, + poll_method=_poll, + poll_debouncer=Debouncer( + hass, + _LOGGER, + cooldown=0, + immediate=True, + ), + ) + assert coordinator.available is False # no data yet + saved_callback = None + + processor = MagicMock() + coordinator.async_register_processor(processor) + async_handle_update = processor.async_handle_update + + def _async_register_callback(_hass, _callback, _matcher, _mode): + nonlocal saved_callback + saved_callback = _callback + return lambda: None + + with patch( + "homeassistant.components.bluetooth.update_coordinator.async_register_callback", + _async_register_callback, + ): + cancel = coordinator.async_start() + + assert saved_callback is not None + + # First poll fails + saved_callback(GENERIC_BLUETOOTH_SERVICE_INFO, BluetoothChange.ADVERTISEMENT) + await hass.async_block_till_done() + assert async_handle_update.mock_calls[-1] == call({"testdata": None}) + + assert ( + "aa:bb:cc:dd:ee:ff: Bluetooth error whilst polling: Connection was aborted" + in caplog.text + ) + + # Second poll works + flag = False + saved_callback(GENERIC_BLUETOOTH_SERVICE_INFO, BluetoothChange.ADVERTISEMENT) + await hass.async_block_till_done() + assert async_handle_update.mock_calls[-1] == call({"testdata": False}) + + cancel() + + async def test_poll_failure_and_recover(hass: HomeAssistant, mock_bleak_scanner_start): """Test error handling and recovery.""" await async_setup_component(hass, DOMAIN, {DOMAIN: {}})