From 59872f1914db0e6b6b043dc69a850c955d974cba Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 8 Apr 2023 17:12:42 -1000 Subject: [PATCH] Reduce bond fallback polling interval when BPUP is alive (#90871) * Reduce bond fallback polling interval when BPUP is alive If push updates are alive we should not check every 10 seconds. * tweak * tweak * coverage * coverage * coverage --- homeassistant/components/bond/entity.py | 29 +++++++---- tests/components/bond/common.py | 7 ++- tests/components/bond/test_diagnostics.py | 9 +++- tests/components/bond/test_fan.py | 60 +++++++++++++++++++++++ 4 files changed, 93 insertions(+), 12 deletions(-) diff --git a/homeassistant/components/bond/entity.py b/homeassistant/components/bond/entity.py index 4e3218ba041..36af3974482 100644 --- a/homeassistant/components/bond/entity.py +++ b/homeassistant/components/bond/entity.py @@ -17,9 +17,9 @@ from homeassistant.const import ( ATTR_SW_VERSION, ATTR_VIA_DEVICE, ) -from homeassistant.core import callback +from homeassistant.core import CALLBACK_TYPE, callback from homeassistant.helpers.entity import DeviceInfo, Entity -from homeassistant.helpers.event import async_track_time_interval +from homeassistant.helpers.event import async_call_later from .const import DOMAIN from .utils import BondDevice, BondHub @@ -27,6 +27,7 @@ from .utils import BondDevice, BondHub _LOGGER = logging.getLogger(__name__) _FALLBACK_SCAN_INTERVAL = timedelta(seconds=10) +_BPUP_ALIVE_SCAN_INTERVAL = timedelta(seconds=60) class BondEntity(Entity): @@ -65,6 +66,7 @@ class BondEntity(Entity): self._attr_name = device.name self._attr_assumed_state = self._hub.is_bridge and not self._device.trust_state self._apply_state() + self._bpup_polling_fallback: CALLBACK_TYPE | None = None @property def device_info(self) -> DeviceInfo: @@ -100,12 +102,13 @@ class BondEntity(Entity): return device_info async def async_update(self) -> None: - """Fetch assumed state of the cover from the hub using API.""" + """Perform a manual update from API.""" await self._async_update_from_api() @callback def _async_update_if_bpup_not_alive(self, now: datetime) -> None: """Fetch via the API if BPUP is not alive.""" + self._async_schedule_bpup_alive_or_poll() if ( self.hass.is_stopping or self._bpup_subs.alive @@ -172,16 +175,22 @@ class BondEntity(Entity): """Subscribe to BPUP and start polling.""" await super().async_added_to_hass() self._bpup_subs.subscribe(self._device_id, self._async_bpup_callback) - self.async_on_remove( - async_track_time_interval( - self.hass, - self._async_update_if_bpup_not_alive, - _FALLBACK_SCAN_INTERVAL, - name=f"Bond {self.entity_id} fallback polling", - ) + self._async_schedule_bpup_alive_or_poll() + + @callback + def _async_schedule_bpup_alive_or_poll(self) -> None: + """Schedule the BPUP alive or poll.""" + alive = self._bpup_subs.alive + self._bpup_polling_fallback = async_call_later( + self.hass, + _BPUP_ALIVE_SCAN_INTERVAL if alive else _FALLBACK_SCAN_INTERVAL, + self._async_update_if_bpup_not_alive, ) async def async_will_remove_from_hass(self) -> None: """Unsubscribe from BPUP data on remove.""" await super().async_will_remove_from_hass() self._bpup_subs.unsubscribe(self._device_id, self._async_bpup_callback) + if self._bpup_polling_fallback: + self._bpup_polling_fallback() + self._bpup_polling_fallback = None diff --git a/tests/components/bond/common.py b/tests/components/bond/common.py index f14efcdf172..9b2e82abc84 100644 --- a/tests/components/bond/common.py +++ b/tests/components/bond/common.py @@ -127,7 +127,12 @@ def patch_bond_version( return nullcontext() if return_value is None: - return_value = {"bondid": "ZXXX12345"} + return_value = { + "bondid": "ZXXX12345", + "target": "test-model", + "fw_ver": "test-version", + "mcu_ver": "test-hw-version", + } return patch( "homeassistant.components.bond.Bond.version", diff --git a/tests/components/bond/test_diagnostics.py b/tests/components/bond/test_diagnostics.py index 238c5c37861..f8d0313ee9c 100644 --- a/tests/components/bond/test_diagnostics.py +++ b/tests/components/bond/test_diagnostics.py @@ -42,5 +42,12 @@ async def test_diagnostics( "data": {"access_token": "**REDACTED**", "host": "some host"}, "title": "Mock Title", }, - "hub": {"version": {"bondid": "ZXXX12345"}}, + "hub": { + "version": { + "bondid": "ZXXX12345", + "fw_ver": "test-version", + "mcu_ver": "test-hw-version", + "target": "test-model", + } + }, } diff --git a/tests/components/bond/test_fan.py b/tests/components/bond/test_fan.py index e97fc40beba..d3c03e0d805 100644 --- a/tests/components/bond/test_fan.py +++ b/tests/components/bond/test_fan.py @@ -468,3 +468,63 @@ async def test_fan_available(hass: HomeAssistant) -> None: await help_test_entity_available( hass, FAN_DOMAIN, ceiling_fan("name-1"), "fan.name_1" ) + + +async def test_setup_smart_by_bond_fan(hass: HomeAssistant) -> None: + """Test setting up a fan without a hub.""" + config_entry = await setup_platform( + hass, + FAN_DOMAIN, + ceiling_fan("name-1"), + bond_device_id="test-device-id", + bond_version={ + "bondid": "KXXX12345", + "target": "test-model", + "fw_ver": "test-version", + "mcu_ver": "test-hw-version", + }, + ) + assert hass.states.get("fan.name_1") is not None + registry = er.async_get(hass) + entry = registry.async_get("fan.name_1") + assert entry.device_id is not None + device_registry = dr.async_get(hass) + device = device_registry.async_get(entry.device_id) + assert device is not None + assert device.sw_version == "test-version" + assert device.manufacturer == "Olibra" + assert device.identifiers == {("bond", "KXXX12345", "test-device-id")} + assert device.hw_version == "test-hw-version" + await hass.config_entries.async_unload(config_entry.entry_id) + await hass.async_block_till_done() + + +async def test_setup_hub_template_fan(hass: HomeAssistant) -> None: + """Test setting up a fan on a hub created from a template.""" + config_entry = await setup_platform( + hass, + FAN_DOMAIN, + {**ceiling_fan("name-1"), "template": "test-template"}, + bond_device_id="test-device-id", + props={"branding_profile": "test-branding-profile"}, + bond_version={ + "bondid": "ZXXX12345", + "target": "test-model", + "fw_ver": "test-version", + "mcu_ver": "test-hw-version", + }, + ) + assert hass.states.get("fan.name_1") is not None + registry = er.async_get(hass) + entry = registry.async_get("fan.name_1") + assert entry.device_id is not None + device_registry = dr.async_get(hass) + device = device_registry.async_get(entry.device_id) + assert device is not None + assert device.sw_version is None + assert device.model == "test-branding-profile test-template" + assert device.manufacturer == "Olibra" + assert device.identifiers == {("bond", "ZXXX12345", "test-device-id")} + assert device.hw_version is None + await hass.config_entries.async_unload(config_entry.entry_id) + await hass.async_block_till_done()