Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Delay all ZHA polling until initialization of entities has completed #105814

Merged
merged 18 commits into from Dec 27, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
58 changes: 31 additions & 27 deletions homeassistant/components/zha/core/device.py
Expand Up @@ -166,6 +166,9 @@ def __init__(

if not self.is_coordinator:
keep_alive_interval = random.randint(*_UPDATE_ALIVE_INTERVAL)
self.debug(
"starting availability checks - interval: %s", keep_alive_interval
)
self.unsubs.append(
async_track_time_interval(
self.hass,
Expand Down Expand Up @@ -447,35 +450,36 @@ async def _check_available(self, *_: Any) -> None:
self._checkins_missed_count = 0
return

if (
self._checkins_missed_count >= _CHECKIN_GRACE_PERIODS
or self.manufacturer == "LUMI"
or not self._endpoints
):
if self.hass.data[const.DATA_ZHA].allow_polling:
if (
self._checkins_missed_count >= _CHECKIN_GRACE_PERIODS
or self.manufacturer == "LUMI"
or not self._endpoints
):
self.debug(
(
"last_seen is %s seconds ago and ping attempts have been exhausted,"
" marking the device unavailable"
),
difference,
)
self.update_available(False)
return

self._checkins_missed_count += 1
self.debug(
(
"last_seen is %s seconds ago and ping attempts have been exhausted,"
" marking the device unavailable"
),
difference,
"Attempting to checkin with device - missed checkins: %s",
self._checkins_missed_count,
)
self.update_available(False)
return

self._checkins_missed_count += 1
self.debug(
"Attempting to checkin with device - missed checkins: %s",
self._checkins_missed_count,
)
if not self.basic_ch:
self.debug("does not have a mandatory basic cluster")
self.update_available(False)
return
res = await self.basic_ch.get_attribute_value(
ATTR_MANUFACTURER, from_cache=False
)
if res is not None:
self._checkins_missed_count = 0
if not self.basic_ch:
self.debug("does not have a mandatory basic cluster")
self.update_available(False)
return
res = await self.basic_ch.get_attribute_value(
ATTR_MANUFACTURER, from_cache=False
)
if res is not None:
self._checkins_missed_count = 0

def update_available(self, available: bool) -> None:
"""Update device availability and signal entities."""
Expand Down
5 changes: 5 additions & 0 deletions homeassistant/components/zha/core/gateway.py
Expand Up @@ -47,6 +47,7 @@
ATTR_TYPE,
CONF_RADIO_TYPE,
CONF_ZIGPY,
DATA_ZHA,
DEBUG_COMP_BELLOWS,
DEBUG_COMP_ZHA,
DEBUG_COMP_ZIGPY,
Expand Down Expand Up @@ -292,6 +293,10 @@ async def fetch_updated_state() -> None:
if dev.is_mains_powered
)
)
_LOGGER.debug(
"completed fetching current state for mains powered devices - allowing polled requests"
)
self.hass.data[DATA_ZHA].allow_polling = True

# background the fetching of state for mains powered devices
self.config_entry.async_create_background_task(
Expand Down
1 change: 1 addition & 0 deletions homeassistant/components/zha/core/helpers.py
Expand Up @@ -442,6 +442,7 @@ class ZHAData:
device_trigger_cache: dict[str, tuple[str, dict]] = dataclasses.field(
default_factory=dict
)
allow_polling: bool = dataclasses.field(default=False)


def get_zha_data(hass: HomeAssistant) -> ZHAData:
Expand Down
29 changes: 24 additions & 5 deletions homeassistant/components/zha/light.py
Expand Up @@ -47,6 +47,7 @@
CONF_ENABLE_ENHANCED_LIGHT_TRANSITION,
CONF_ENABLE_LIGHT_TRANSITIONING_FLAG,
CONF_GROUP_MEMBERS_ASSUME_STATE,
DATA_ZHA,
SIGNAL_ADD_ENTITIES,
SIGNAL_ATTR_UPDATED,
SIGNAL_SET_LEVEL,
Expand Down Expand Up @@ -75,7 +76,6 @@

STRICT_MATCH = functools.partial(ZHA_ENTITIES.strict_match, Platform.LIGHT)
GROUP_MATCH = functools.partial(ZHA_ENTITIES.group_match, Platform.LIGHT)
PARALLEL_UPDATES = 0
SIGNAL_LIGHT_GROUP_STATE_CHANGED = "zha_light_group_state_changed"
SIGNAL_LIGHT_GROUP_TRANSITION_START = "zha_light_group_transition_start"
SIGNAL_LIGHT_GROUP_TRANSITION_FINISHED = "zha_light_group_transition_finished"
Expand Down Expand Up @@ -788,6 +788,7 @@ async def async_added_to_hass(self) -> None:
self._cancel_refresh_handle = async_track_time_interval(
self.hass, self._refresh, timedelta(seconds=refresh_interval)
)
self.debug("started polling with refresh interval of %s", refresh_interval)
self.async_accept_signal(
None,
SIGNAL_LIGHT_GROUP_STATE_CHANGED,
Expand Down Expand Up @@ -838,6 +839,8 @@ async def async_will_remove_from_hass(self) -> None:
"""Disconnect entity object when removed."""
assert self._cancel_refresh_handle
self._cancel_refresh_handle()
self._cancel_refresh_handle = None
self.debug("stopped polling during device removal")
await super().async_will_remove_from_hass()

@callback
Expand Down Expand Up @@ -980,17 +983,33 @@ async def _refresh(self, time):
if self.is_transitioning:
self.debug("skipping _refresh while transitioning")
return
await self.async_get_state()
self.async_write_ha_state()
if self._zha_device.available and self.hass.data[DATA_ZHA].allow_polling:
self.debug("polling for updated state")
await self.async_get_state()
self.async_write_ha_state()
else:
self.debug(
"skipping polling for updated state, available: %s, allow polled requests: %s",
self._zha_device.available,
self.hass.data[DATA_ZHA].allow_polling,
)

async def _maybe_force_refresh(self, signal):
"""Force update the state if the signal contains the entity id for this entity."""
if self.entity_id in signal["entity_ids"]:
if self.is_transitioning:
self.debug("skipping _maybe_force_refresh while transitioning")
return
await self.async_get_state()
self.async_write_ha_state()
if self._zha_device.available and self.hass.data[DATA_ZHA].allow_polling:
self.debug("forcing polling for updated state")
await self.async_get_state()
self.async_write_ha_state()
else:
self.debug(
"skipping _maybe_force_refresh, available: %s, allow polled requests: %s",
self._zha_device.available,
self.hass.data[DATA_ZHA].allow_polling,
)

@callback
def _assume_group_state(self, signal, update_params) -> None:
Expand Down