diff --git a/homeassistant/components/hue/light.py b/homeassistant/components/hue/light.py index 7ed2dcc84f2..2e27bf65c98 100644 --- a/homeassistant/components/hue/light.py +++ b/homeassistant/components/hue/light.py @@ -79,17 +79,19 @@ async def async_setup_entry(hass, config_entry, async_add_entities): light_coordinator = DataUpdateCoordinator( hass, _LOGGER, - "light", - partial(async_safe_fetch, bridge, bridge.api.lights.update), - SCAN_INTERVAL, - Debouncer(bridge.hass, _LOGGER, REQUEST_REFRESH_DELAY, True), + name="light", + update_method=partial(async_safe_fetch, bridge, bridge.api.lights.update), + update_interval=SCAN_INTERVAL, + request_refresh_debouncer=Debouncer( + bridge.hass, _LOGGER, cooldown=REQUEST_REFRESH_DELAY, immediate=True + ), ) # First do a refresh to see if we can reach the hub. # Otherwise we will declare not ready. await light_coordinator.async_refresh() - if light_coordinator.failed_last_update: + if not light_coordinator.last_update_success: raise PlatformNotReady update_lights = partial( @@ -122,10 +124,12 @@ async def async_setup_entry(hass, config_entry, async_add_entities): group_coordinator = DataUpdateCoordinator( hass, _LOGGER, - "group", - partial(async_safe_fetch, bridge, bridge.api.groups.update), - SCAN_INTERVAL, - Debouncer(bridge.hass, _LOGGER, REQUEST_REFRESH_DELAY, True), + name="group", + update_method=partial(async_safe_fetch, bridge, bridge.api.groups.update), + update_interval=SCAN_INTERVAL, + request_refresh_debouncer=Debouncer( + bridge.hass, _LOGGER, cooldown=REQUEST_REFRESH_DELAY, immediate=True + ), ) update_groups = partial( @@ -277,7 +281,7 @@ class HueLight(Light): @property def available(self): """Return if light is available.""" - return not self.coordinator.failed_last_update and ( + return self.coordinator.last_update_success and ( self.is_group or self.bridge.allow_unreachable or self.light.state["reachable"] diff --git a/homeassistant/components/hue/sensor_base.py b/homeassistant/components/hue/sensor_base.py index f57b0f98d30..1e518c05ee5 100644 --- a/homeassistant/components/hue/sensor_base.py +++ b/homeassistant/components/hue/sensor_base.py @@ -42,10 +42,12 @@ class SensorManager: self.coordinator = DataUpdateCoordinator( bridge.hass, _LOGGER, - "sensor", - self.async_update_data, - self.SCAN_INTERVAL, - debounce.Debouncer(bridge.hass, _LOGGER, REQUEST_REFRESH_DELAY, True), + name="sensor", + update_method=self.async_update_data, + update_interval=self.SCAN_INTERVAL, + request_refresh_debouncer=debounce.Debouncer( + bridge.hass, _LOGGER, cooldown=REQUEST_REFRESH_DELAY, immediate=True + ), ) async def async_update_data(self): @@ -183,7 +185,7 @@ class GenericHueSensor(entity.Entity): @property def available(self): """Return if sensor is available.""" - return not self.bridge.sensor_manager.coordinator.failed_last_update and ( + return self.bridge.sensor_manager.coordinator.last_update_success and ( self.bridge.allow_unreachable or self.sensor.config["reachable"] ) diff --git a/homeassistant/components/updater/__init__.py b/homeassistant/components/updater/__init__.py index 0f388964bb0..0a2c6697f69 100644 --- a/homeassistant/components/updater/__init__.py +++ b/homeassistant/components/updater/__init__.py @@ -123,7 +123,11 @@ async def async_setup(hass, config): return Updater(update_available, newest, release_notes) coordinator = hass.data[DOMAIN] = update_coordinator.DataUpdateCoordinator( - hass, _LOGGER, "Home Assistant update", check_new_version, timedelta(days=1) + hass, + _LOGGER, + name="Home Assistant update", + update_method=check_new_version, + update_interval=timedelta(days=1), ) await coordinator.async_refresh() diff --git a/homeassistant/components/updater/binary_sensor.py b/homeassistant/components/updater/binary_sensor.py index 60f5cfedf6e..7abab616d5c 100644 --- a/homeassistant/components/updater/binary_sensor.py +++ b/homeassistant/components/updater/binary_sensor.py @@ -38,7 +38,7 @@ class UpdaterBinary(BinarySensorDevice): @property def available(self) -> bool: """Return True if entity is available.""" - return not self.coordinator.failed_last_update + return self.coordinator.last_update_success @property def should_poll(self) -> bool: diff --git a/homeassistant/helpers/debounce.py b/homeassistant/helpers/debounce.py index 5bacbdb7d11..bbaf6dacfeb 100644 --- a/homeassistant/helpers/debounce.py +++ b/homeassistant/helpers/debounce.py @@ -13,6 +13,7 @@ class Debouncer: self, hass: HomeAssistant, logger: Logger, + *, cooldown: float, immediate: bool, function: Optional[Callable[..., Awaitable[Any]]] = None, diff --git a/homeassistant/helpers/update_coordinator.py b/homeassistant/helpers/update_coordinator.py index a882e2880b1..5f0490b6ea2 100644 --- a/homeassistant/helpers/update_coordinator.py +++ b/homeassistant/helpers/update_coordinator.py @@ -26,6 +26,7 @@ class DataUpdateCoordinator: self, hass: HomeAssistant, logger: logging.Logger, + *, name: str, update_method: Callable[[], Awaitable], update_interval: timedelta, @@ -43,16 +44,20 @@ class DataUpdateCoordinator: self._listeners: List[CALLBACK_TYPE] = [] self._unsub_refresh: Optional[CALLBACK_TYPE] = None self._request_refresh_task: Optional[asyncio.TimerHandle] = None - self.failed_last_update = False + self.last_update_success = True + if request_refresh_debouncer is None: request_refresh_debouncer = Debouncer( hass, logger, - REQUEST_REFRESH_DEFAULT_COOLDOWN, - REQUEST_REFRESH_DEFAULT_IMMEDIATE, + cooldown=REQUEST_REFRESH_DEFAULT_COOLDOWN, + immediate=REQUEST_REFRESH_DEFAULT_IMMEDIATE, + function=self.async_refresh, ) + else: + request_refresh_debouncer.function = self.async_refresh + self._debounced_refresh = request_refresh_debouncer - request_refresh_debouncer.function = self.async_refresh @callback def async_add_listener(self, update_callback: CALLBACK_TYPE) -> None: @@ -110,19 +115,19 @@ class DataUpdateCoordinator: self.data = await self.update_method() except UpdateFailed as err: - if not self.failed_last_update: + if self.last_update_success: self.logger.error("Error fetching %s data: %s", self.name, err) - self.failed_last_update = True + self.last_update_success = False except Exception as err: # pylint: disable=broad-except - self.failed_last_update = True + self.last_update_success = False self.logger.exception( "Unexpected error fetching %s data: %s", self.name, err ) else: - if self.failed_last_update: - self.failed_last_update = False + if not self.last_update_success: + self.last_update_success = True self.logger.info("Fetching %s data recovered") finally: diff --git a/tests/components/hue/test_light.py b/tests/components/hue/test_light.py index df3fe5f8998..d57c15bfa36 100644 --- a/tests/components/hue/test_light.py +++ b/tests/components/hue/test_light.py @@ -703,7 +703,7 @@ def test_available(): colorgamuttype=LIGHT_GAMUT_TYPE, colorgamut=LIGHT_GAMUT, ), - coordinator=Mock(failed_last_update=False), + coordinator=Mock(last_update_success=True), bridge=Mock(allow_unreachable=False), is_group=False, ) @@ -717,7 +717,7 @@ def test_available(): colorgamuttype=LIGHT_GAMUT_TYPE, colorgamut=LIGHT_GAMUT, ), - coordinator=Mock(failed_last_update=False), + coordinator=Mock(last_update_success=True), bridge=Mock(allow_unreachable=True), is_group=False, ) @@ -731,7 +731,7 @@ def test_available(): colorgamuttype=LIGHT_GAMUT_TYPE, colorgamut=LIGHT_GAMUT, ), - coordinator=Mock(failed_last_update=False), + coordinator=Mock(last_update_success=True), bridge=Mock(allow_unreachable=False), is_group=True, ) @@ -748,7 +748,7 @@ def test_hs_color(): colorgamuttype=LIGHT_GAMUT_TYPE, colorgamut=LIGHT_GAMUT, ), - coordinator=Mock(failed_last_update=False), + coordinator=Mock(last_update_success=True), bridge=Mock(), is_group=False, ) @@ -762,7 +762,7 @@ def test_hs_color(): colorgamuttype=LIGHT_GAMUT_TYPE, colorgamut=LIGHT_GAMUT, ), - coordinator=Mock(failed_last_update=False), + coordinator=Mock(last_update_success=True), bridge=Mock(), is_group=False, ) @@ -776,7 +776,7 @@ def test_hs_color(): colorgamuttype=LIGHT_GAMUT_TYPE, colorgamut=LIGHT_GAMUT, ), - coordinator=Mock(failed_last_update=False), + coordinator=Mock(last_update_success=True), bridge=Mock(), is_group=False, ) diff --git a/tests/helpers/test_debounce.py b/tests/helpers/test_debounce.py index d7629a393a9..4972fbbc018 100644 --- a/tests/helpers/test_debounce.py +++ b/tests/helpers/test_debounce.py @@ -8,7 +8,11 @@ async def test_immediate_works(hass): """Test immediate works.""" calls = [] debouncer = debounce.Debouncer( - hass, None, 0.01, True, CoroutineMock(side_effect=lambda: calls.append(None)) + hass, + None, + cooldown=0.01, + immediate=True, + function=CoroutineMock(side_effect=lambda: calls.append(None)), ) await debouncer.async_call() @@ -37,7 +41,11 @@ async def test_not_immediate_works(hass): """Test immediate works.""" calls = [] debouncer = debounce.Debouncer( - hass, None, 0.01, False, CoroutineMock(side_effect=lambda: calls.append(None)) + hass, + None, + cooldown=0.01, + immediate=False, + function=CoroutineMock(side_effect=lambda: calls.append(None)), ) await debouncer.async_call() diff --git a/tests/helpers/test_update_coordinator.py b/tests/helpers/test_update_coordinator.py index 8c792506833..04fd180b60d 100644 --- a/tests/helpers/test_update_coordinator.py +++ b/tests/helpers/test_update_coordinator.py @@ -23,7 +23,11 @@ def crd(hass): return len(calls) crd = update_coordinator.DataUpdateCoordinator( - hass, LOGGER, "test", refresh, timedelta(seconds=10), + hass, + LOGGER, + name="test", + update_method=refresh, + update_interval=timedelta(seconds=10), ) return crd @@ -33,7 +37,7 @@ async def test_async_refresh(crd): assert crd.data is None await crd.async_refresh() assert crd.data == 1 - assert crd.failed_last_update is False + assert crd.last_update_success is True updates = [] @@ -58,12 +62,12 @@ async def test_request_refresh(crd): assert crd.data is None await crd.async_request_refresh() assert crd.data == 1 - assert crd.failed_last_update is False + assert crd.last_update_success is True # Second time we hit the debonuce await crd.async_request_refresh() assert crd.data == 1 - assert crd.failed_last_update is False + assert crd.last_update_success is True async def test_refresh_fail(crd, caplog): @@ -73,7 +77,7 @@ async def test_refresh_fail(crd, caplog): await crd.async_refresh() assert crd.data is None - assert crd.failed_last_update is True + assert crd.last_update_success is False assert "Error fetching test data" in caplog.text crd.update_method = CoroutineMock(return_value=1) @@ -81,7 +85,7 @@ async def test_refresh_fail(crd, caplog): await crd.async_refresh() assert crd.data == 1 - assert crd.failed_last_update is False + assert crd.last_update_success is True crd.update_method = CoroutineMock(side_effect=ValueError) caplog.clear() @@ -89,7 +93,7 @@ async def test_refresh_fail(crd, caplog): await crd.async_refresh() assert crd.data == 1 # value from previous fetch - assert crd.failed_last_update is True + assert crd.last_update_success is False assert "Unexpected error fetching test data" in caplog.text