- rewrote initialization (resolves #99)

This commit is contained in:
Jack Simbach 2022-09-05 16:08:56 -04:00
parent c3729685f9
commit f2553c9bbc
11 changed files with 170 additions and 150 deletions

View File

@ -7,7 +7,9 @@ import voluptuous as vol
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.const import CONF_REGION
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
from .const import DOMAIN
from .exceptions import HaAuthError, HaCannotConnect
from .update_coordinator import GeHomeUpdateCoordinator
_LOGGER = logging.getLogger(__name__)
@ -42,9 +44,14 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
coordinator = GeHomeUpdateCoordinator(hass, entry)
hass.data[DOMAIN][entry.entry_id] = coordinator
if not await coordinator.async_setup():
return False
try:
if not await coordinator.async_setup():
return False
except HaCannotConnect:
raise ConfigEntryNotReady("Could not connect to SmartHQ")
except HaAuthError:
raise ConfigEntryAuthFailed("Could not authenticate to SmartHQ")
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, coordinator.shutdown)
return True

View File

@ -5,30 +5,34 @@ from typing import Callable
from homeassistant.components.switch import SwitchEntity
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from .const import DOMAIN
from .devices import ApplianceApi
from .entities import GeErdBinarySensor
from .update_coordinator import GeHomeUpdateCoordinator
_LOGGER = logging.getLogger(__name__)
async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry, async_add_entities: Callable):
"""GE Home sensors."""
"""GE Home binary sensors."""
_LOGGER.debug('Adding GE Binary Sensor Entities')
coordinator: GeHomeUpdateCoordinator = hass.data[DOMAIN][config_entry.entry_id]
#apis = coordinator.appliance_apis.values()
# This should be a NOP, but let's be safe
with async_timeout.timeout(20):
await coordinator.initialization_future
@callback
def async_devices_discovered(apis: list[ApplianceApi]):
_LOGGER.debug(f'Found {len(apis):d} appliance APIs')
apis = coordinator.appliance_apis.values()
_LOGGER.debug(f'Found {len(apis):d} appliance APIs')
entities = [
entity
for api in apis
for entity in api.entities
if isinstance(entity, GeErdBinarySensor) and not isinstance(entity, SwitchEntity)
]
_LOGGER.debug(f'Found {len(entities):d} binary sensors ')
async_add_entities(entities)
entities = [
entity
for api in apis
for entity in api.entities
if isinstance(entity, GeErdBinarySensor) and not isinstance(entity, SwitchEntity)
]
_LOGGER.debug(f'Found {len(entities):d} binary sensors')
async_add_entities(entities)
async_dispatcher_connect(hass, coordinator.signal_ready, async_devices_discovered)

View File

@ -4,9 +4,11 @@ import logging
from typing import Callable
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from .const import DOMAIN
from .devices import ApplianceApi
from .entities import GeErdButton
from .update_coordinator import GeHomeUpdateCoordinator
@ -15,19 +17,19 @@ _LOGGER = logging.getLogger(__name__)
async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry, async_add_entities: Callable):
"""GE Home buttons."""
_LOGGER.debug('Adding GE Button Entities')
coordinator: GeHomeUpdateCoordinator = hass.data[DOMAIN][config_entry.entry_id]
# This should be a NOP, but let's be safe
with async_timeout.timeout(20):
await coordinator.initialization_future
@callback
def async_devices_discovered(apis: list[ApplianceApi]):
_LOGGER.debug(f'Found {len(apis):d} appliance APIs')
entities = [
entity
for api in apis
for entity in api.entities
if isinstance(entity, GeErdButton)
]
_LOGGER.debug(f'Found {len(entities):d} buttons ')
async_add_entities(entities)
apis = coordinator.appliance_apis.values()
_LOGGER.debug(f'Found {len(apis):d} appliance APIs')
entities = [
entity
for api in apis
for entity in api.entities
if isinstance(entity, GeErdButton)
]
_LOGGER.debug(f'Found {len(entities):d} buttons ')
async_add_entities(entities)
async_dispatcher_connect(hass, coordinator.signal_ready, async_devices_discovered)

View File

@ -5,31 +5,32 @@ from typing import Callable
from homeassistant.components.climate import ClimateEntity
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from .entities import GeClimate
from .const import DOMAIN
from .devices import ApplianceApi
from .update_coordinator import GeHomeUpdateCoordinator
_LOGGER = logging.getLogger(__name__)
async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry, async_add_entities: Callable):
"""GE Climate Devices."""
_LOGGER.debug('Adding GE Climate Entities')
coordinator: GeHomeUpdateCoordinator = hass.data[DOMAIN][config_entry.entry_id]
# This should be a NOP, but let's be safe
with async_timeout.timeout(20):
await coordinator.initialization_future
_LOGGER.debug('Coordinator init future finished')
@callback
def async_devices_discovered(apis: list[ApplianceApi]):
_LOGGER.debug(f'Found {len(apis):d} appliance APIs')
entities = [
entity
for api in apis
for entity in api.entities
if isinstance(entity, GeClimate)
]
_LOGGER.debug(f'Found {len(entities):d} climate entities')
async_add_entities(entities)
apis = list(coordinator.appliance_apis.values())
_LOGGER.debug(f'Found {len(apis):d} appliance APIs')
entities = [
entity
for api in apis
for entity in api.entities
if isinstance(entity, GeClimate)
]
_LOGGER.debug(f'Found {len(entities):d} climate entities')
async_add_entities(entities)
async_dispatcher_connect(hass, coordinator.signal_ready, async_devices_discovered)

View File

@ -4,10 +4,13 @@ import logging
from typing import Callable
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from .const import DOMAIN
from .entities import GeErdLight
from .devices import ApplianceApi
from .update_coordinator import GeHomeUpdateCoordinator
_LOGGER = logging.getLogger(__name__)
@ -20,19 +23,17 @@ async def async_setup_entry(
_LOGGER.debug("Adding GE Home lights")
coordinator: GeHomeUpdateCoordinator = hass.data[DOMAIN][config_entry.entry_id]
# This should be a NOP, but let's be safe
with async_timeout.timeout(20):
await coordinator.initialization_future
_LOGGER.debug("Coordinator init future finished")
@callback
def async_devices_discovered(apis: list[ApplianceApi]):
_LOGGER.debug(f"Found {len(apis):d} appliance APIs")
entities = [
entity
for api in apis
for entity in api.entities
if isinstance(entity, GeErdLight)
and entity.erd_code in api.appliance._property_cache
]
_LOGGER.debug(f"Found {len(entities):d} lights")
async_add_entities(entities)
apis = list(coordinator.appliance_apis.values())
_LOGGER.debug(f"Found {len(apis):d} appliance APIs")
entities = [
entity
for api in apis
for entity in api.entities
if isinstance(entity, GeErdLight)
and entity.erd_code in api.appliance._property_cache
]
_LOGGER.debug(f"Found {len(entities):d} lights")
async_add_entities(entities)
async_dispatcher_connect(hass, coordinator.signal_ready, async_devices_discovered)

View File

@ -4,9 +4,12 @@ import logging
from typing import Callable
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from .const import DOMAIN
from .devices import ApplianceApi
from .entities import GeErdNumber
from .update_coordinator import GeHomeUpdateCoordinator
@ -15,19 +18,19 @@ _LOGGER = logging.getLogger(__name__)
async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry, async_add_entities: Callable):
"""GE Home numbers."""
_LOGGER.debug('Adding GE Number Entities')
coordinator: GeHomeUpdateCoordinator = hass.data[DOMAIN][config_entry.entry_id]
# This should be a NOP, but let's be safe
with async_timeout.timeout(20):
await coordinator.initialization_future
@callback
def async_devices_discovered(apis: list[ApplianceApi]):
_LOGGER.debug(f'Found {len(apis):d} appliance APIs')
entities = [
entity
for api in apis
for entity in api.entities
if isinstance(entity, GeErdNumber)
]
_LOGGER.debug(f'Found {len(entities):d} numbers ')
async_add_entities(entities)
apis = coordinator.appliance_apis.values()
_LOGGER.debug(f'Found {len(apis):d} appliance APIs')
entities = [
entity
for api in apis
for entity in api.entities
if isinstance(entity, GeErdNumber)
]
_LOGGER.debug(f'Found {len(entities):d} numbers ')
async_add_entities(entities)
async_dispatcher_connect(hass, coordinator.signal_ready, async_devices_discovered)

View File

@ -4,9 +4,11 @@ import logging
from typing import Callable
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from .const import DOMAIN
from .devices import ApplianceApi
from .entities import GeErdSelect
from .update_coordinator import GeHomeUpdateCoordinator
@ -20,19 +22,17 @@ async def async_setup_entry(
_LOGGER.debug("Adding GE Home selects")
coordinator: GeHomeUpdateCoordinator = hass.data[DOMAIN][config_entry.entry_id]
# This should be a NOP, but let's be safe
with async_timeout.timeout(20):
await coordinator.initialization_future
_LOGGER.debug("Coordinator init future finished")
@callback
def async_devices_discovered(apis: list[ApplianceApi]):
_LOGGER.debug(f"Found {len(apis):d} appliance APIs")
entities = [
entity
for api in apis
for entity in api.entities
if isinstance(entity, GeErdSelect)
and entity.erd_code in api.appliance._property_cache
]
_LOGGER.debug(f"Found {len(entities):d} selectors")
async_add_entities(entities)
apis = list(coordinator.appliance_apis.values())
_LOGGER.debug(f"Found {len(apis):d} appliance APIs")
entities = [
entity
for api in apis
for entity in api.entities
if isinstance(entity, GeErdSelect)
and entity.erd_code in api.appliance._property_cache
]
_LOGGER.debug(f"Found {len(entities):d} selectors")
async_add_entities(entities)
async_dispatcher_connect(hass, coordinator.signal_ready, async_devices_discovered)

View File

@ -6,7 +6,8 @@ import voluptuous as vol
from datetime import timedelta
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers import entity_platform
from .const import (
@ -16,6 +17,7 @@ from .const import (
SERVICE_SET_INT_VALUE
)
from .entities import GeErdSensor
from .devices import ApplianceApi
from .update_coordinator import GeHomeUpdateCoordinator
ATTR_DURATION = "duration"
@ -31,21 +33,19 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry, asyn
# Get the platform
platform = entity_platform.async_get_current_platform()
# This should be a NOP, but let's be safe
with async_timeout.timeout(20):
await coordinator.initialization_future
_LOGGER.debug('Coordinator init future finished')
@callback
def async_devices_discovered(apis: list[ApplianceApi]):
_LOGGER.debug(f'Found {len(apis):d} appliance APIs')
entities = [
entity
for api in apis
for entity in api.entities
if isinstance(entity, GeErdSensor) and entity.erd_code in api.appliance._property_cache
]
_LOGGER.debug(f'Found {len(entities):d} sensors')
async_add_entities(entities)
apis = list(coordinator.appliance_apis.values())
_LOGGER.debug(f'Found {len(apis):d} appliance APIs')
entities = [
entity
for api in apis
for entity in api.entities
if isinstance(entity, GeErdSensor) and entity.erd_code in api.appliance._property_cache
]
_LOGGER.debug(f'Found {len(entities):d} sensors')
async_add_entities(entities)
async_dispatcher_connect(hass, coordinator.signal_ready, async_devices_discovered)
# register set_timer entity service
platform.async_register_entity_service(

View File

@ -4,10 +4,13 @@ import logging
from typing import Callable
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from .entities import GeErdSwitch
from .const import DOMAIN
from .devices import ApplianceApi
from .update_coordinator import GeHomeUpdateCoordinator
_LOGGER = logging.getLogger(__name__)
@ -17,18 +20,16 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry, asyn
_LOGGER.debug('Adding GE Home switches')
coordinator: GeHomeUpdateCoordinator = hass.data[DOMAIN][config_entry.entry_id]
# This should be a NOP, but let's be safe
with async_timeout.timeout(20):
await coordinator.initialization_future
_LOGGER.debug('Coordinator init future finished')
@callback
def async_devices_discovered(apis: list[ApplianceApi]):
_LOGGER.debug(f'Found {len(apis):d} appliance APIs')
entities = [
entity
for api in apis
for entity in api.entities
if isinstance(entity, GeErdSwitch) and entity.erd_code in api.appliance._property_cache
]
_LOGGER.debug(f'Found {len(entities):d} switches')
async_add_entities(entities)
apis = list(coordinator.appliance_apis.values())
_LOGGER.debug(f'Found {len(apis):d} appliance APIs')
entities = [
entity
for api in apis
for entity in api.entities
if isinstance(entity, GeErdSwitch) and entity.erd_code in api.appliance._property_cache
]
_LOGGER.debug(f'Found {len(entities):d} switches')
async_add_entities(entities)
async_dispatcher_connect(hass, coordinator.signal_ready, async_devices_discovered)

View File

@ -21,6 +21,7 @@ from .exceptions import HaAuthError, HaCannotConnect
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.dispatcher import async_dispatcher_send
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
from .const import (
@ -64,7 +65,6 @@ class GeHomeUpdateCoordinator(DataUpdateCoordinator):
self._got_roster = False
self._init_done = False
self._retry_count = 0
self.initialization_future = asyncio.Future()
def create_ge_client(
self, event_loop: Optional[asyncio.AbstractEventLoop]
@ -95,6 +95,11 @@ class GeHomeUpdateCoordinator(DataUpdateCoordinator):
def appliance_apis(self) -> Dict[str, ApplianceApi]:
return self._appliance_apis
@property
def signal_ready(self) -> str:
"""Event specific per entry to signal readiness"""
return f"{DOMAIN}-ready-{self._config_entry.entry_id}"
@property
def online(self) -> bool:
"""
@ -167,12 +172,6 @@ class GeHomeUpdateCoordinator(DataUpdateCoordinator):
except Exception:
raise HaCannotConnect("Unknown connection failure")
try:
with async_timeout.timeout(ASYNC_TIMEOUT):
await self.initialization_future
except (asyncio.CancelledError, asyncio.TimeoutError):
raise HaCannotConnect("Initialization timed out")
return True
async def async_start_client(self):
@ -322,16 +321,17 @@ class GeHomeUpdateCoordinator(DataUpdateCoordinator):
async def async_maybe_trigger_all_ready(self):
"""See if we're all ready to go, and if so, let the games begin."""
if self._init_done or self.initialization_future.done():
if self._init_done:
# Been here, done this
return
if self._got_roster and self.all_appliances_updated:
_LOGGER.debug("Ready to go. Waiting 2 seconds and setting init future result.")
# The the flag and wait to prevent two different fun race conditions
_LOGGER.debug("Ready to go, sending ready signal")
self._init_done = True
await asyncio.sleep(2)
self.initialization_future.set_result(True)
await self.client.async_event(EVENT_ALL_APPLIANCES_READY, None)
async_dispatcher_send(
self.hass,
self.signal_ready,
list(self.appliance_apis.values()))
def _get_retry_delay(self) -> int:
delay = MIN_RETRY_DELAY * 2 ** (self._retry_count - 1)

View File

@ -5,10 +5,13 @@ from typing import Callable
from homeassistant.components.water_heater import WaterHeaterEntity
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from .entities import GeAbstractWaterHeater
from .const import DOMAIN
from .devices import ApplianceApi
from .update_coordinator import GeHomeUpdateCoordinator
_LOGGER = logging.getLogger(__name__)
@ -18,18 +21,16 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry, asyn
_LOGGER.debug('Adding GE "Water Heaters"')
coordinator: GeHomeUpdateCoordinator = hass.data[DOMAIN][config_entry.entry_id]
# This should be a NOP, but let's be safe
with async_timeout.timeout(20):
await coordinator.initialization_future
_LOGGER.debug('Coordinator init future finished')
@callback
def async_devices_discovered(apis: list[ApplianceApi]):
_LOGGER.debug(f'Found {len(apis):d} appliance APIs')
entities = [
entity
for api in apis
for entity in api.entities
if isinstance(entity, GeAbstractWaterHeater)
]
_LOGGER.debug(f'Found {len(entities):d} "water heaters"')
async_add_entities(entities)
apis = list(coordinator.appliance_apis.values())
_LOGGER.debug(f'Found {len(apis):d} appliance APIs')
entities = [
entity
for api in apis
for entity in api.entities
if isinstance(entity, GeAbstractWaterHeater)
]
_LOGGER.debug(f'Found {len(entities):d} "water heaters"')
async_add_entities(entities)
async_dispatcher_connect(hass, coordinator.signal_ready, async_devices_discovered)