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
This commit is contained in:
J. Nick Koston 2023-04-08 17:12:42 -10:00 committed by GitHub
parent 8fe597b7c6
commit 59872f1914
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 93 additions and 12 deletions

View file

@ -17,9 +17,9 @@ from homeassistant.const import (
ATTR_SW_VERSION, ATTR_SW_VERSION,
ATTR_VIA_DEVICE, 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.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 .const import DOMAIN
from .utils import BondDevice, BondHub from .utils import BondDevice, BondHub
@ -27,6 +27,7 @@ from .utils import BondDevice, BondHub
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
_FALLBACK_SCAN_INTERVAL = timedelta(seconds=10) _FALLBACK_SCAN_INTERVAL = timedelta(seconds=10)
_BPUP_ALIVE_SCAN_INTERVAL = timedelta(seconds=60)
class BondEntity(Entity): class BondEntity(Entity):
@ -65,6 +66,7 @@ class BondEntity(Entity):
self._attr_name = device.name self._attr_name = device.name
self._attr_assumed_state = self._hub.is_bridge and not self._device.trust_state self._attr_assumed_state = self._hub.is_bridge and not self._device.trust_state
self._apply_state() self._apply_state()
self._bpup_polling_fallback: CALLBACK_TYPE | None = None
@property @property
def device_info(self) -> DeviceInfo: def device_info(self) -> DeviceInfo:
@ -100,12 +102,13 @@ class BondEntity(Entity):
return device_info return device_info
async def async_update(self) -> None: 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() await self._async_update_from_api()
@callback @callback
def _async_update_if_bpup_not_alive(self, now: datetime) -> None: def _async_update_if_bpup_not_alive(self, now: datetime) -> None:
"""Fetch via the API if BPUP is not alive.""" """Fetch via the API if BPUP is not alive."""
self._async_schedule_bpup_alive_or_poll()
if ( if (
self.hass.is_stopping self.hass.is_stopping
or self._bpup_subs.alive or self._bpup_subs.alive
@ -172,16 +175,22 @@ class BondEntity(Entity):
"""Subscribe to BPUP and start polling.""" """Subscribe to BPUP and start polling."""
await super().async_added_to_hass() await super().async_added_to_hass()
self._bpup_subs.subscribe(self._device_id, self._async_bpup_callback) self._bpup_subs.subscribe(self._device_id, self._async_bpup_callback)
self.async_on_remove( self._async_schedule_bpup_alive_or_poll()
async_track_time_interval(
self.hass, @callback
self._async_update_if_bpup_not_alive, def _async_schedule_bpup_alive_or_poll(self) -> None:
_FALLBACK_SCAN_INTERVAL, """Schedule the BPUP alive or poll."""
name=f"Bond {self.entity_id} fallback polling", 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: async def async_will_remove_from_hass(self) -> None:
"""Unsubscribe from BPUP data on remove.""" """Unsubscribe from BPUP data on remove."""
await super().async_will_remove_from_hass() await super().async_will_remove_from_hass()
self._bpup_subs.unsubscribe(self._device_id, self._async_bpup_callback) 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

View file

@ -127,7 +127,12 @@ def patch_bond_version(
return nullcontext() return nullcontext()
if return_value is None: 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( return patch(
"homeassistant.components.bond.Bond.version", "homeassistant.components.bond.Bond.version",

View file

@ -42,5 +42,12 @@ async def test_diagnostics(
"data": {"access_token": "**REDACTED**", "host": "some host"}, "data": {"access_token": "**REDACTED**", "host": "some host"},
"title": "Mock Title", "title": "Mock Title",
}, },
"hub": {"version": {"bondid": "ZXXX12345"}}, "hub": {
"version": {
"bondid": "ZXXX12345",
"fw_ver": "test-version",
"mcu_ver": "test-hw-version",
"target": "test-model",
}
},
} }

View file

@ -468,3 +468,63 @@ async def test_fan_available(hass: HomeAssistant) -> None:
await help_test_entity_available( await help_test_entity_available(
hass, FAN_DOMAIN, ceiling_fan("name-1"), "fan.name_1" 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()