diff --git a/homeassistant/components/cloud/__init__.py b/homeassistant/components/cloud/__init__.py index 07c2898f204..360e726c89e 100644 --- a/homeassistant/components/cloud/__init__.py +++ b/homeassistant/components/cloud/__init__.py @@ -1,6 +1,8 @@ """Component to integrate the Home Assistant cloud.""" +from __future__ import annotations + import asyncio -from collections.abc import Callable +from collections.abc import Awaitable, Callable from enum import Enum from hass_nabucasa import Cloud @@ -152,7 +154,8 @@ def async_is_connected(hass: HomeAssistant) -> bool: @callback def async_listen_connection_change( - hass: HomeAssistant, target: Callable[[CloudConnectionState], None] + hass: HomeAssistant, + target: Callable[[CloudConnectionState], Awaitable[None] | None], ) -> Callable[[], None]: """Notify on connection state changes.""" return async_dispatcher_connect(hass, SIGNAL_CLOUD_CONNECTION_STATE, target) diff --git a/homeassistant/components/netatmo/__init__.py b/homeassistant/components/netatmo/__init__.py index ac11ee554a8..f6e43b29653 100644 --- a/homeassistant/components/netatmo/__init__.py +++ b/homeassistant/components/netatmo/__init__.py @@ -31,10 +31,7 @@ from homeassistant.helpers import ( config_entry_oauth2_flow, config_validation as cv, ) -from homeassistant.helpers.dispatcher import ( - async_dispatcher_connect, - async_dispatcher_send, -) +from homeassistant.helpers.dispatcher import async_dispatcher_send from homeassistant.helpers.event import async_call_later from homeassistant.helpers.typing import ConfigType @@ -54,7 +51,6 @@ from .const import ( OAUTH2_AUTHORIZE, OAUTH2_TOKEN, PLATFORMS, - WEBHOOK_ACTIVATION, WEBHOOK_DEACTIVATION, WEBHOOK_PUSH_TYPE, ) @@ -150,8 +146,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: hass.config_entries.async_setup_platforms(entry, PLATFORMS) - _webhook_retries = 0 - async def unregister_webhook( call_or_event_or_dt: ServiceCall | Event | datetime | None, ) -> None: @@ -171,11 +165,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: "No webhook to be dropped for %s", entry.data[CONF_WEBHOOK_ID] ) - nonlocal _webhook_retries - if _webhook_retries < MAX_WEBHOOK_RETRIES: - _webhook_retries += 1 - async_call_later(hass, 30, register_webhook) - async def register_webhook( call_or_event_or_dt: ServiceCall | Event | datetime | None, ) -> None: @@ -184,14 +173,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: hass.config_entries.async_update_entry(entry, data=data) if cloud.async_active_subscription(hass): - if CONF_CLOUDHOOK_URL not in entry.data: - webhook_url = await cloud.async_create_cloudhook( - hass, entry.data[CONF_WEBHOOK_ID] - ) - data = {**entry.data, CONF_CLOUDHOOK_URL: webhook_url} - hass.config_entries.async_update_entry(entry, data=data) - else: - webhook_url = entry.data[CONF_CLOUDHOOK_URL] + webhook_url = await async_cloudhook_generate_url(hass, entry) else: webhook_url = webhook_generate_url(hass, entry.data[CONF_WEBHOOK_ID]) @@ -204,32 +186,15 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: ) return + webhook_register( + hass, + DOMAIN, + "Netatmo", + entry.data[CONF_WEBHOOK_ID], + async_handle_webhook, + ) + try: - webhook_register( - hass, - DOMAIN, - "Netatmo", - entry.data[CONF_WEBHOOK_ID], - async_handle_webhook, - ) - - async def handle_event(event: dict) -> None: - """Handle webhook events.""" - if event["data"][WEBHOOK_PUSH_TYPE] == WEBHOOK_ACTIVATION: - if activation_listener is not None: - activation_listener() - - if activation_timeout is not None: - activation_timeout() - - activation_listener = async_dispatcher_connect( - hass, - f"signal-{DOMAIN}-webhook-None", - handle_event, - ) - - activation_timeout = async_call_later(hass, 30, unregister_webhook) - await hass.data[DOMAIN][entry.entry_id][AUTH].async_addwebhook(webhook_url) _LOGGER.info("Register Netatmo webhook: %s", webhook_url) except pyatmo.ApiError as err: @@ -239,10 +204,24 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, unregister_webhook) ) - if hass.state == CoreState.running: - await register_webhook(None) + async def manage_cloudhook(state: cloud.CloudConnectionState) -> None: + if state is cloud.CloudConnectionState.CLOUD_CONNECTED: + await register_webhook(None) + + if state is cloud.CloudConnectionState.CLOUD_DISCONNECTED: + await unregister_webhook(None) + async_call_later(hass, 30, register_webhook) + + if cloud.async_active_subscription(hass): + if cloud.async_is_connected(hass): + await register_webhook(None) + cloud.async_listen_connection_change(hass, manage_cloudhook) + else: - hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STARTED, register_webhook) + if hass.state == CoreState.running: + await register_webhook(None) + else: + hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STARTED, register_webhook) hass.services.async_register(DOMAIN, "register_webhook", register_webhook) hass.services.async_register(DOMAIN, "unregister_webhook", unregister_webhook) @@ -252,6 +231,18 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: return True +async def async_cloudhook_generate_url(hass: HomeAssistant, entry: ConfigEntry) -> str: + """Generate the full URL for a webhook_id.""" + if CONF_CLOUDHOOK_URL not in entry.data: + webhook_url = await cloud.async_create_cloudhook( + hass, entry.data[CONF_WEBHOOK_ID] + ) + data = {**entry.data, CONF_CLOUDHOOK_URL: webhook_url} + hass.config_entries.async_update_entry(entry, data=data) + return webhook_url + return str(entry.data[CONF_CLOUDHOOK_URL]) + + async def async_config_entry_updated(hass: HomeAssistant, entry: ConfigEntry) -> None: """Handle signals of config entry being updated.""" async_dispatcher_send(hass, f"signal-{DOMAIN}-public-update-{entry.entry_id}") diff --git a/tests/components/netatmo/test_init.py b/tests/components/netatmo/test_init.py index ffa68d75011..911fd6c309a 100644 --- a/tests/components/netatmo/test_init.py +++ b/tests/components/netatmo/test_init.py @@ -182,6 +182,8 @@ async def test_setup_with_cloud(hass, config_entry): with patch( "homeassistant.components.cloud.async_is_logged_in", return_value=True + ), patch( + "homeassistant.components.cloud.async_is_connected", return_value=True ), patch( "homeassistant.components.cloud.async_active_subscription", return_value=True ), patch( @@ -203,6 +205,7 @@ async def test_setup_with_cloud(hass, config_entry): hass, "netatmo", {"netatmo": {"client_id": "123", "client_secret": "abc"}} ) assert hass.components.cloud.async_active_subscription() is True + assert hass.components.cloud.async_is_connected() is True fake_create_cloudhook.assert_called_once() assert ( @@ -245,6 +248,8 @@ async def test_setup_with_cloudhook(hass): with patch( "homeassistant.components.cloud.async_is_logged_in", return_value=True + ), patch( + "homeassistant.components.cloud.async_is_connected", return_value=True ), patch( "homeassistant.components.cloud.async_active_subscription", return_value=True ), patch(