From e3e2e817e51e082ae0216763ac330a2ac35a0a68 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 5 Apr 2020 17:47:27 -0500 Subject: [PATCH] Convert rachio to cloudhooks (#33724) * Convert rachio to cloudhooks * add cloud to after_dependencies --- homeassistant/components/rachio/__init__.py | 27 +++++--- homeassistant/components/rachio/const.py | 3 + homeassistant/components/rachio/manifest.json | 1 + homeassistant/components/rachio/webhooks.py | 61 +++++++++++++------ 4 files changed, 65 insertions(+), 27 deletions(-) diff --git a/homeassistant/components/rachio/__init__.py b/homeassistant/components/rachio/__init__.py index 8879bd6965c..a5c9f5ab0a9 100644 --- a/homeassistant/components/rachio/__init__.py +++ b/homeassistant/components/rachio/__init__.py @@ -13,14 +13,18 @@ from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.helpers import config_validation as cv from .const import ( - CONF_CUSTOM_URL, + CONF_CLOUDHOOK_URL, CONF_MANUAL_RUN_MINS, + CONF_WEBHOOK_ID, DEFAULT_MANUAL_RUN_MINS, DOMAIN, RACHIO_API_EXCEPTIONS, ) from .device import RachioPerson -from .webhooks import WEBHOOK_PATH, RachioWebhookView +from .webhooks import ( + async_get_or_create_registered_webhook_id_and_url, + async_register_webhook, +) _LOGGER = logging.getLogger(__name__) @@ -31,7 +35,6 @@ CONFIG_SCHEMA = vol.Schema( DOMAIN: vol.Schema( { vol.Required(CONF_API_KEY): cv.string, - vol.Optional(CONF_CUSTOM_URL): cv.string, vol.Optional( CONF_MANUAL_RUN_MINS, default=DEFAULT_MANUAL_RUN_MINS ): cv.positive_int, @@ -76,6 +79,12 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): return unload_ok +async def async_remove_entry(hass, entry): + """Remove a rachio config entry.""" + if CONF_CLOUDHOOK_URL in entry.data: + await hass.components.cloud.async_delete_cloudhook(entry.data[CONF_WEBHOOK_ID]) + + async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): """Set up the Rachio config entry.""" @@ -93,11 +102,11 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): rachio = Rachio(api_key) # Get the URL of this server - custom_url = config.get(CONF_CUSTOM_URL) - hass_url = hass.config.api.base_url if custom_url is None else custom_url rachio.webhook_auth = secrets.token_hex() - webhook_url_path = f"{WEBHOOK_PATH}-{entry.entry_id}" - rachio.webhook_url = f"{hass_url}{webhook_url_path}" + webhook_id, webhook_url = await async_get_or_create_registered_webhook_id_and_url( + hass, entry + ) + rachio.webhook_url = webhook_url person = RachioPerson(rachio, entry) @@ -118,9 +127,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): # Enable component hass.data[DOMAIN][entry.entry_id] = person - - # Listen for incoming webhook connections after the data is there - hass.http.register_view(RachioWebhookView(entry.entry_id, webhook_url_path)) + async_register_webhook(hass, webhook_id, entry.entry_id) for component in SUPPORTED_DOMAINS: hass.async_create_task( diff --git a/homeassistant/components/rachio/const.py b/homeassistant/components/rachio/const.py index 2e73bf9b116..3508e24eb4b 100644 --- a/homeassistant/components/rachio/const.py +++ b/homeassistant/components/rachio/const.py @@ -58,3 +58,6 @@ SIGNAL_RACHIO_UPDATE = f"{DOMAIN}_update" SIGNAL_RACHIO_CONTROLLER_UPDATE = f"{SIGNAL_RACHIO_UPDATE}_controller" SIGNAL_RACHIO_ZONE_UPDATE = f"{SIGNAL_RACHIO_UPDATE}_zone" SIGNAL_RACHIO_SCHEDULE_UPDATE = f"{SIGNAL_RACHIO_UPDATE}_schedule" + +CONF_WEBHOOK_ID = "webhook_id" +CONF_CLOUDHOOK_URL = "cloudhook_url" diff --git a/homeassistant/components/rachio/manifest.json b/homeassistant/components/rachio/manifest.json index b8c141f52ab..c289d754006 100644 --- a/homeassistant/components/rachio/manifest.json +++ b/homeassistant/components/rachio/manifest.json @@ -4,6 +4,7 @@ "documentation": "https://www.home-assistant.io/integrations/rachio", "requirements": ["rachiopy==0.1.3"], "dependencies": ["http"], + "after_dependencies": ["cloud"], "codeowners": ["@bdraco"], "config_flow": true, "homekit": { diff --git a/homeassistant/components/rachio/webhooks.py b/homeassistant/components/rachio/webhooks.py index e31b32e9410..a5960b8b28b 100644 --- a/homeassistant/components/rachio/webhooks.py +++ b/homeassistant/components/rachio/webhooks.py @@ -4,11 +4,13 @@ import logging from aiohttp import web -from homeassistant.components.http import HomeAssistantView from homeassistant.const import URL_API +from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_send from .const import ( + CONF_CLOUDHOOK_URL, + CONF_WEBHOOK_ID, DOMAIN, KEY_EXTERNAL_ID, KEY_TYPE, @@ -68,28 +70,17 @@ SIGNAL_MAP = { _LOGGER = logging.getLogger(__name__) -class RachioWebhookView(HomeAssistantView): - """Provide a page for the server to call.""" +@callback +def async_register_webhook(hass, webhook_id, entry_id): + """Register a webhook.""" - requires_auth = False # Handled separately - - def __init__(self, entry_id, webhook_url): - """Initialize the instance of the view.""" - self._entry_id = entry_id - self.url = webhook_url - self.name = webhook_url[1:].replace("/", ":") - _LOGGER.debug( - "Initialize webhook at url: %s, with name %s", self.url, self.name - ) - - async def post(self, request) -> web.Response: + async def _async_handle_rachio_webhook(hass, webhook_id, request): """Handle webhook calls from the server.""" - hass = request.app["hass"] data = await request.json() try: auth = data.get(KEY_EXTERNAL_ID, "").split(":")[1] - assert auth == hass.data[DOMAIN][self._entry_id].rachio.webhook_auth + assert auth == hass.data[DOMAIN][entry_id].rachio.webhook_auth except (AssertionError, IndexError): return web.Response(status=web.HTTPForbidden.status_code) @@ -98,3 +89,39 @@ class RachioWebhookView(HomeAssistantView): async_dispatcher_send(hass, SIGNAL_MAP[update_type], data) return web.Response(status=web.HTTPNoContent.status_code) + + hass.components.webhook.async_register( + DOMAIN, "Rachio", webhook_id, _async_handle_rachio_webhook + ) + + +async def async_get_or_create_registered_webhook_id_and_url(hass, entry): + """Generate webhook ID.""" + config = entry.data.copy() + + updated_config = False + webhook_url = None + + webhook_id = config.get(CONF_WEBHOOK_ID) + if not webhook_id: + webhook_id = hass.components.webhook.async_generate_id() + config[CONF_WEBHOOK_ID] = webhook_id + updated_config = True + + if hass.components.cloud.async_active_subscription(): + cloudhook_url = config.get(CONF_CLOUDHOOK_URL) + if not cloudhook_url: + cloudhook_url = await hass.components.cloud.async_create_cloudhook( + webhook_id + ) + config[CONF_CLOUDHOOK_URL] = cloudhook_url + updated_config = True + webhook_url = cloudhook_url + + if not webhook_url: + webhook_url = hass.components.webhook.async_generate_url(webhook_id) + + if updated_config: + hass.config_entries.async_update_entry(entry, data=config) + + return webhook_id, webhook_url