Clean up stale ZHA database listener when reconnecting to radio (#101850)

This commit is contained in:
puddly 2023-10-20 02:48:33 -04:00 committed by GitHub
parent 92e625636a
commit 8a79870e3a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 84 additions and 42 deletions

View File

@ -12,8 +12,8 @@ from zigpy.config import CONF_DATABASE, CONF_DEVICE, CONF_DEVICE_PATH
from zigpy.exceptions import NetworkSettingsInconsistent
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_TYPE
from homeassistant.core import HomeAssistant
from homeassistant.const import CONF_TYPE, EVENT_HOMEASSISTANT_STOP
from homeassistant.core import Event, HomeAssistant
from homeassistant.exceptions import ConfigEntryNotReady, HomeAssistantError
from homeassistant.helpers import device_registry as dr
import homeassistant.helpers.config_validation as cv
@ -160,19 +160,6 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b
zha_gateway = ZHAGateway(hass, zha_data.yaml_config, config_entry)
async def async_zha_shutdown():
"""Handle shutdown tasks."""
await zha_gateway.shutdown()
# clean up any remaining entity metadata
# (entities that have been discovered but not yet added to HA)
# suppress KeyError because we don't know what state we may
# be in when we get here in failure cases
with contextlib.suppress(KeyError):
for platform in PLATFORMS:
del zha_data.platforms[platform]
config_entry.async_on_unload(async_zha_shutdown)
try:
await zha_gateway.async_initialize()
except NetworkSettingsInconsistent as exc:
@ -211,6 +198,13 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b
websocket_api.async_load_api(hass)
async def async_shutdown(_: Event) -> None:
await zha_gateway.shutdown()
config_entry.async_on_unload(
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, async_shutdown)
)
await zha_gateway.async_initialize_devices_and_entities()
await hass.config_entries.async_forward_entry_setups(config_entry, PLATFORMS)
async_dispatcher_send(hass, SIGNAL_ADD_ENTITIES)
@ -220,7 +214,18 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b
async def async_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:
"""Unload ZHA config entry."""
zha_data = get_zha_data(hass)
zha_data.gateway = None
if zha_data.gateway is not None:
await zha_data.gateway.shutdown()
zha_data.gateway = None
# clean up any remaining entity metadata
# (entities that have been discovered but not yet added to HA)
# suppress KeyError because we don't know what state we may
# be in when we get here in failure cases
with contextlib.suppress(KeyError):
for platform in PLATFORMS:
del zha_data.platforms[platform]
GROUP_PROBE.cleanup()
websocket_api.async_unload_api(hass)

View File

@ -203,28 +203,33 @@ class ZHAGateway:
start_radio=False,
)
for attempt in range(STARTUP_RETRIES):
try:
await self.application_controller.startup(auto_form=True)
except NetworkSettingsInconsistent:
raise
except TransientConnectionError as exc:
raise ConfigEntryNotReady from exc
except Exception as exc: # pylint: disable=broad-except
_LOGGER.warning(
"Couldn't start %s coordinator (attempt %s of %s)",
self.radio_description,
attempt + 1,
STARTUP_RETRIES,
exc_info=exc,
)
try:
for attempt in range(STARTUP_RETRIES):
try:
await self.application_controller.startup(auto_form=True)
except TransientConnectionError as exc:
raise ConfigEntryNotReady from exc
except NetworkSettingsInconsistent:
raise
except Exception as exc: # pylint: disable=broad-except
_LOGGER.debug(
"Couldn't start %s coordinator (attempt %s of %s)",
self.radio_description,
attempt + 1,
STARTUP_RETRIES,
exc_info=exc,
)
if attempt == STARTUP_RETRIES - 1:
raise exc
if attempt == STARTUP_RETRIES - 1:
raise exc
await asyncio.sleep(STARTUP_FAILURE_DELAY_S)
else:
break
await asyncio.sleep(STARTUP_FAILURE_DELAY_S)
else:
break
except Exception:
# Explicitly shut down the controller application on failure
await self.application_controller.shutdown()
raise
zha_data = get_zha_data(self.hass)
zha_data.gateway = self

View File

@ -437,7 +437,10 @@ class ZHAData:
def get_zha_data(hass: HomeAssistant) -> ZHAData:
"""Get the global ZHA data object."""
return hass.data.get(DATA_ZHA, ZHAData())
if DATA_ZHA not in hass.data:
hass.data[DATA_ZHA] = ZHAData()
return hass.data[DATA_ZHA]
def get_zha_gateway(hass: HomeAssistant) -> ZHAGateway:

View File

@ -26,7 +26,7 @@
"pyserial-asyncio==0.6",
"zha-quirks==0.0.105",
"zigpy-deconz==0.21.1",
"zigpy==0.57.2",
"zigpy==0.58.1",
"zigpy-xbee==0.18.3",
"zigpy-zigate==0.11.0",
"zigpy-znp==0.11.6",

View File

@ -2813,7 +2813,7 @@ zigpy-zigate==0.11.0
zigpy-znp==0.11.6
# homeassistant.components.zha
zigpy==0.57.2
zigpy==0.58.1
# homeassistant.components.zoneminder
zm-py==0.5.2

View File

@ -2101,7 +2101,7 @@ zigpy-zigate==0.11.0
zigpy-znp==0.11.6
# homeassistant.components.zha
zigpy==0.57.2
zigpy==0.58.1
# homeassistant.components.zwave_js
zwave-js-server-python==0.52.1

View File

@ -13,8 +13,14 @@ from homeassistant.components.zha.core.const import (
CONF_USB_PATH,
DOMAIN,
)
from homeassistant.const import MAJOR_VERSION, MINOR_VERSION, Platform
from homeassistant.core import HomeAssistant
from homeassistant.components.zha.core.helpers import get_zha_data
from homeassistant.const import (
EVENT_HOMEASSISTANT_STOP,
MAJOR_VERSION,
MINOR_VERSION,
Platform,
)
from homeassistant.core import CoreState, HomeAssistant
from homeassistant.helpers.event import async_call_later
from homeassistant.setup import async_setup_component
@ -203,3 +209,26 @@ async def test_zha_retry_unique_ids(
await hass.config_entries.async_unload(config_entry.entry_id)
assert "does not generate unique IDs" not in caplog.text
async def test_shutdown_on_ha_stop(
hass: HomeAssistant,
config_entry: MockConfigEntry,
mock_zigpy_connect: ControllerApplication,
) -> None:
"""Test that the ZHA gateway is stopped when HA is shut down."""
config_entry.add_to_hass(hass)
await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done()
zha_data = get_zha_data(hass)
with patch.object(
zha_data.gateway, "shutdown", wraps=zha_data.gateway.shutdown
) as mock_shutdown:
hass.bus.async_fire(EVENT_HOMEASSISTANT_STOP)
hass.state = CoreState.stopping
await hass.async_block_till_done()
assert len(mock_shutdown.mock_calls) == 1