From 2197bf9772442401258c938d2633d1c00920120d Mon Sep 17 00:00:00 2001 From: "Jean-Philippe (JP) Cornil" Date: Sun, 9 Apr 2023 19:28:06 +0200 Subject: [PATCH] Fix for #18: Workaround attempting to schedule yaml after restore from 'storage' (.storage/core.entity_registry) Notes: HA is calling integration's async_setup_entry (__init__.py) to restore all components in 'storage' before calling components async_setup_platform (binary_sensor.py and switch.py) for yaml which is fine but effective processing of these tasks is taking place asynchronously and in random order while yaml should always the latest (it should overload storage). This is only an issue for integration supporting both configuration options simultaneously (yaml and configflow as it uses storage) which is the case here. Goal of the workaround is therefore to delay import from configuration.yaml until all entities in storage have been restored. Because async_setup_platform (yaml) is called after all sync_setup_entry calls from storage, a counter can be incremented for each such call and block the former until all sync_setup_entry complete and decrement the counter back to zero. This counter is implemented within a new SetupEntryStatus class used as a contextmanager guarding processing of each entry setup in async_setup_entry. --- custom_components/mcp23017/__init__.py | 33 +++++++++++++++++---- custom_components/mcp23017/binary_sensor.py | 6 +++- custom_components/mcp23017/switch.py | 7 ++++- 3 files changed, 39 insertions(+), 7 deletions(-) diff --git a/custom_components/mcp23017/__init__.py b/custom_components/mcp23017/__init__.py index 9e908aa..e93d682 100644 --- a/custom_components/mcp23017/__init__.py +++ b/custom_components/mcp23017/__init__.py @@ -53,6 +53,23 @@ MCP23017_DATA_LOCK = asyncio.Lock() +class SetupEntryStatus: + """Class registering the number of outstanding async_setup_entry calls.""" + def __init__(self): + """Initialize call counter.""" + self.number = 0 + def __enter__(self): + """Increment call counter (with statement).""" + self.number +=1 + def __exit__(self, exc_type, exc_value, exc_tb): + """Decrement call counter (with statement).""" + self.number -=1 + def busy(self): + """Return True when there is at least one outstanding call""" + return self.number != 0 + +setup_entry_status = SetupEntryStatus() + async def async_setup(hass, config): """Set up the component.""" @@ -81,19 +98,18 @@ def stop_polling(event): async def async_setup_entry(hass, config_entry): """Set up the MCP23017 from a config entry.""" - # Forward entry setup to configured platform - hass.async_create_task( - hass.config_entries.async_forward_entry_setup( + # Register this setup instance + with setup_entry_status: + # Forward entry setup to configured platform + await hass.config_entries.async_forward_entry_setup( config_entry, config_entry.data[CONF_FLOW_PLATFORM] ) - ) return True async def async_unload_entry(hass, config_entry): """Unload entity from MCP23017 component and platform.""" - # Unload related platform await hass.config_entries.async_forward_entry_unload( config_entry, config_entry.data[CONF_FLOW_PLATFORM] @@ -124,6 +140,13 @@ async def async_unload_entry(hass, config_entry): type(component).__name__, i2c_address, ) + else: + _LOGGER.warning( + "%s@0x%02x component not found, unable to unload entity (pin %d).", + type(component).__name__, + i2c_address, + config_entry.data[CONF_FLOW_PIN_NUMBER], + ) return True diff --git a/custom_components/mcp23017/binary_sensor.py b/custom_components/mcp23017/binary_sensor.py index 5567172..4c6e709 100644 --- a/custom_components/mcp23017/binary_sensor.py +++ b/custom_components/mcp23017/binary_sensor.py @@ -8,7 +8,7 @@ from homeassistant.components.binary_sensor import PLATFORM_SCHEMA, BinarySensorEntity from homeassistant.helpers.device_registry import DeviceEntryType -from . import async_get_or_create +from . import async_get_or_create, setup_entry_status from homeassistant.config_entries import SOURCE_IMPORT from homeassistant.core import callback import homeassistant.helpers.config_validation as cv @@ -48,6 +48,10 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up the MCP23017 platform for binary_sensor entities.""" + # Wait for configflow to terminate before processing configuration.yaml + while setup_entry_status.busy(): + await asyncio.sleep(0) + for pin_number, pin_name in config[CONF_PINS].items(): hass.async_create_task( hass.config_entries.flow.async_init( diff --git a/custom_components/mcp23017/switch.py b/custom_components/mcp23017/switch.py index 914ca79..565f6d6 100644 --- a/custom_components/mcp23017/switch.py +++ b/custom_components/mcp23017/switch.py @@ -1,11 +1,12 @@ """Platform for mcp23017-based switch.""" +import asyncio import functools import logging import voluptuous as vol -from . import async_get_or_create +from . import async_get_or_create, setup_entry_status from homeassistant.components.switch import PLATFORM_SCHEMA, ToggleEntity from homeassistant.helpers.device_registry import DeviceEntryType from homeassistant.config_entries import SOURCE_IMPORT @@ -43,6 +44,10 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up the MCP23017 for switch entities.""" + # Wait for configflow to terminate before processing configuration.yaml + while setup_entry_status.busy(): + await asyncio.sleep(0) + for pin_number, pin_name in config[CONF_PINS].items(): hass.async_create_task( hass.config_entries.flow.async_init(