2019-04-03 15:40:03 +00:00
|
|
|
"""Integration with the Rachio Iro sprinkler system controller."""
|
2018-07-01 15:54:51 +00:00
|
|
|
import asyncio
|
|
|
|
import logging
|
2019-12-12 15:46:33 +00:00
|
|
|
import secrets
|
2018-09-11 09:21:48 +00:00
|
|
|
from typing import Optional
|
2018-07-01 15:54:51 +00:00
|
|
|
|
|
|
|
from aiohttp import web
|
2019-12-02 23:59:13 +00:00
|
|
|
from rachiopy import Rachio
|
2018-07-01 15:54:51 +00:00
|
|
|
import voluptuous as vol
|
2019-12-02 23:59:13 +00:00
|
|
|
|
2018-07-01 15:54:51 +00:00
|
|
|
from homeassistant.components.http import HomeAssistantView
|
2020-03-14 05:46:17 +00:00
|
|
|
from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry
|
2018-07-01 15:54:51 +00:00
|
|
|
from homeassistant.const import CONF_API_KEY, EVENT_HOMEASSISTANT_STOP, URL_API
|
2020-03-14 05:46:17 +00:00
|
|
|
from homeassistant.core import HomeAssistant
|
|
|
|
from homeassistant.exceptions import ConfigEntryNotReady
|
2020-03-16 02:01:41 +00:00
|
|
|
from homeassistant.helpers import config_validation as cv, device_registry
|
2018-07-01 15:54:51 +00:00
|
|
|
from homeassistant.helpers.dispatcher import async_dispatcher_send
|
2020-03-16 02:01:41 +00:00
|
|
|
from homeassistant.helpers.entity import Entity
|
2018-07-01 15:54:51 +00:00
|
|
|
|
2020-03-14 05:46:17 +00:00
|
|
|
from .const import (
|
|
|
|
CONF_CUSTOM_URL,
|
|
|
|
CONF_MANUAL_RUN_MINS,
|
|
|
|
DEFAULT_MANUAL_RUN_MINS,
|
2020-03-16 02:01:41 +00:00
|
|
|
DEFAULT_NAME,
|
2020-03-14 05:46:17 +00:00
|
|
|
DOMAIN,
|
|
|
|
KEY_DEVICES,
|
|
|
|
KEY_ENABLED,
|
|
|
|
KEY_EXTERNAL_ID,
|
|
|
|
KEY_ID,
|
|
|
|
KEY_MAC_ADDRESS,
|
2020-03-16 02:01:41 +00:00
|
|
|
KEY_MODEL,
|
2020-03-14 05:46:17 +00:00
|
|
|
KEY_NAME,
|
|
|
|
KEY_SERIAL_NUMBER,
|
|
|
|
KEY_STATUS,
|
|
|
|
KEY_TYPE,
|
|
|
|
KEY_USERNAME,
|
|
|
|
KEY_ZONES,
|
|
|
|
RACHIO_API_EXCEPTIONS,
|
|
|
|
)
|
2018-07-01 15:54:51 +00:00
|
|
|
|
2020-03-14 05:46:17 +00:00
|
|
|
_LOGGER = logging.getLogger(__name__)
|
2018-07-01 15:54:51 +00:00
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
SUPPORTED_DOMAINS = ["switch", "binary_sensor"]
|
2018-09-27 21:17:15 +00:00
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
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,
|
|
|
|
}
|
|
|
|
)
|
|
|
|
},
|
|
|
|
extra=vol.ALLOW_EXTRA,
|
|
|
|
)
|
2018-07-01 15:54:51 +00:00
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
|
|
|
|
STATUS_ONLINE = "ONLINE"
|
|
|
|
STATUS_OFFLINE = "OFFLINE"
|
2018-07-01 15:54:51 +00:00
|
|
|
|
|
|
|
# Device webhook values
|
2019-07-31 19:25:30 +00:00
|
|
|
TYPE_CONTROLLER_STATUS = "DEVICE_STATUS"
|
|
|
|
SUBTYPE_OFFLINE = "OFFLINE"
|
|
|
|
SUBTYPE_ONLINE = "ONLINE"
|
|
|
|
SUBTYPE_OFFLINE_NOTIFICATION = "OFFLINE_NOTIFICATION"
|
|
|
|
SUBTYPE_COLD_REBOOT = "COLD_REBOOT"
|
|
|
|
SUBTYPE_SLEEP_MODE_ON = "SLEEP_MODE_ON"
|
|
|
|
SUBTYPE_SLEEP_MODE_OFF = "SLEEP_MODE_OFF"
|
|
|
|
SUBTYPE_BROWNOUT_VALVE = "BROWNOUT_VALVE"
|
|
|
|
SUBTYPE_RAIN_SENSOR_DETECTION_ON = "RAIN_SENSOR_DETECTION_ON"
|
|
|
|
SUBTYPE_RAIN_SENSOR_DETECTION_OFF = "RAIN_SENSOR_DETECTION_OFF"
|
|
|
|
SUBTYPE_RAIN_DELAY_ON = "RAIN_DELAY_ON"
|
|
|
|
SUBTYPE_RAIN_DELAY_OFF = "RAIN_DELAY_OFF"
|
2018-07-01 15:54:51 +00:00
|
|
|
|
|
|
|
# Schedule webhook values
|
2019-07-31 19:25:30 +00:00
|
|
|
TYPE_SCHEDULE_STATUS = "SCHEDULE_STATUS"
|
|
|
|
SUBTYPE_SCHEDULE_STARTED = "SCHEDULE_STARTED"
|
|
|
|
SUBTYPE_SCHEDULE_STOPPED = "SCHEDULE_STOPPED"
|
|
|
|
SUBTYPE_SCHEDULE_COMPLETED = "SCHEDULE_COMPLETED"
|
|
|
|
SUBTYPE_WEATHER_NO_SKIP = "WEATHER_INTELLIGENCE_NO_SKIP"
|
|
|
|
SUBTYPE_WEATHER_SKIP = "WEATHER_INTELLIGENCE_SKIP"
|
|
|
|
SUBTYPE_WEATHER_CLIMATE_SKIP = "WEATHER_INTELLIGENCE_CLIMATE_SKIP"
|
|
|
|
SUBTYPE_WEATHER_FREEZE = "WEATHER_INTELLIGENCE_FREEZE"
|
2018-07-01 15:54:51 +00:00
|
|
|
|
|
|
|
# Zone webhook values
|
2019-07-31 19:25:30 +00:00
|
|
|
TYPE_ZONE_STATUS = "ZONE_STATUS"
|
|
|
|
SUBTYPE_ZONE_STARTED = "ZONE_STARTED"
|
|
|
|
SUBTYPE_ZONE_STOPPED = "ZONE_STOPPED"
|
|
|
|
SUBTYPE_ZONE_COMPLETED = "ZONE_COMPLETED"
|
|
|
|
SUBTYPE_ZONE_CYCLING = "ZONE_CYCLING"
|
|
|
|
SUBTYPE_ZONE_CYCLING_COMPLETED = "ZONE_CYCLING_COMPLETED"
|
2018-07-01 15:54:51 +00:00
|
|
|
|
|
|
|
# Webhook callbacks
|
2019-07-31 19:25:30 +00:00
|
|
|
LISTEN_EVENT_TYPES = ["DEVICE_STATUS_EVENT", "ZONE_STATUS_EVENT"]
|
|
|
|
WEBHOOK_CONST_ID = "homeassistant.rachio:"
|
2018-07-01 15:54:51 +00:00
|
|
|
WEBHOOK_PATH = URL_API + DOMAIN
|
2019-07-31 19:25:30 +00:00
|
|
|
SIGNAL_RACHIO_UPDATE = DOMAIN + "_update"
|
|
|
|
SIGNAL_RACHIO_CONTROLLER_UPDATE = SIGNAL_RACHIO_UPDATE + "_controller"
|
|
|
|
SIGNAL_RACHIO_ZONE_UPDATE = SIGNAL_RACHIO_UPDATE + "_zone"
|
|
|
|
SIGNAL_RACHIO_SCHEDULE_UPDATE = SIGNAL_RACHIO_UPDATE + "_schedule"
|
2018-07-01 15:54:51 +00:00
|
|
|
|
|
|
|
|
2020-03-14 05:46:17 +00:00
|
|
|
async def async_setup(hass: HomeAssistant, config: dict):
|
|
|
|
"""Set up the rachio component from YAML."""
|
|
|
|
|
|
|
|
conf = config.get(DOMAIN)
|
|
|
|
hass.data.setdefault(DOMAIN, {})
|
|
|
|
|
|
|
|
if not conf:
|
|
|
|
return True
|
|
|
|
|
|
|
|
hass.async_create_task(
|
|
|
|
hass.config_entries.flow.async_init(
|
|
|
|
DOMAIN, context={"source": SOURCE_IMPORT}, data=conf
|
|
|
|
)
|
|
|
|
)
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
|
|
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry):
|
|
|
|
"""Unload a config entry."""
|
|
|
|
unload_ok = all(
|
|
|
|
await asyncio.gather(
|
|
|
|
*[
|
|
|
|
hass.config_entries.async_forward_entry_unload(entry, component)
|
|
|
|
for component in SUPPORTED_DOMAINS
|
|
|
|
]
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
if unload_ok:
|
|
|
|
hass.data[DOMAIN].pop(entry.entry_id)
|
2018-07-01 15:54:51 +00:00
|
|
|
|
2020-03-14 05:46:17 +00:00
|
|
|
return unload_ok
|
|
|
|
|
|
|
|
|
|
|
|
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
|
|
|
|
"""Set up the Rachio config entry."""
|
|
|
|
|
|
|
|
config = entry.data
|
|
|
|
options = entry.options
|
|
|
|
|
|
|
|
# CONF_MANUAL_RUN_MINS can only come from a yaml import
|
|
|
|
if not options.get(CONF_MANUAL_RUN_MINS) and config.get(CONF_MANUAL_RUN_MINS):
|
2020-03-16 02:01:41 +00:00
|
|
|
options_copy = options.copy()
|
|
|
|
options_copy[CONF_MANUAL_RUN_MINS] = config[CONF_MANUAL_RUN_MINS]
|
|
|
|
hass.config_entries.async_update_entry(options=options_copy)
|
2018-07-01 15:54:51 +00:00
|
|
|
|
|
|
|
# Configure API
|
2020-03-16 02:01:41 +00:00
|
|
|
api_key = config[CONF_API_KEY]
|
2018-07-01 15:54:51 +00:00
|
|
|
rachio = Rachio(api_key)
|
|
|
|
|
|
|
|
# Get the URL of this server
|
2020-03-14 05:46:17 +00:00
|
|
|
custom_url = config.get(CONF_CUSTOM_URL)
|
2018-07-01 15:54:51 +00:00
|
|
|
hass_url = hass.config.api.base_url if custom_url is None else custom_url
|
2019-12-12 15:46:33 +00:00
|
|
|
rachio.webhook_auth = secrets.token_hex()
|
2020-03-14 05:46:17 +00:00
|
|
|
webhook_url_path = f"{WEBHOOK_PATH}-{entry.entry_id}"
|
|
|
|
rachio.webhook_url = f"{hass_url}{webhook_url_path}"
|
2018-07-01 15:54:51 +00:00
|
|
|
|
2020-03-16 02:01:41 +00:00
|
|
|
person = RachioPerson(rachio, entry)
|
|
|
|
|
2018-07-01 15:54:51 +00:00
|
|
|
# Get the API user
|
|
|
|
try:
|
2020-03-16 02:01:41 +00:00
|
|
|
await hass.async_add_executor_job(person.setup, hass)
|
2020-03-14 05:46:17 +00:00
|
|
|
# Yes we really do get all these exceptions (hopefully rachiopy switches to requests)
|
|
|
|
# and there is not a reasonable timeout here so it can block for a long time
|
|
|
|
except RACHIO_API_EXCEPTIONS as error:
|
2018-07-01 15:54:51 +00:00
|
|
|
_LOGGER.error("Could not reach the Rachio API: %s", error)
|
2020-03-14 05:46:17 +00:00
|
|
|
raise ConfigEntryNotReady
|
2018-07-01 15:54:51 +00:00
|
|
|
|
|
|
|
# Check for Rachio controller devices
|
|
|
|
if not person.controllers:
|
2019-07-31 19:25:30 +00:00
|
|
|
_LOGGER.error("No Rachio devices found in account %s", person.username)
|
2018-07-01 15:54:51 +00:00
|
|
|
return False
|
2018-07-23 08:16:05 +00:00
|
|
|
_LOGGER.info("%d Rachio device(s) found", len(person.controllers))
|
2018-07-01 15:54:51 +00:00
|
|
|
|
|
|
|
# Enable component
|
2020-03-14 05:46:17 +00:00
|
|
|
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))
|
2018-09-27 21:17:15 +00:00
|
|
|
|
|
|
|
for component in SUPPORTED_DOMAINS:
|
2020-03-14 05:46:17 +00:00
|
|
|
hass.async_create_task(
|
|
|
|
hass.config_entries.async_forward_entry_setup(entry, component)
|
|
|
|
)
|
2018-09-27 21:17:15 +00:00
|
|
|
|
2018-07-01 15:54:51 +00:00
|
|
|
return True
|
|
|
|
|
|
|
|
|
2018-07-20 08:45:20 +00:00
|
|
|
class RachioPerson:
|
2018-07-01 15:54:51 +00:00
|
|
|
"""Represent a Rachio user."""
|
|
|
|
|
2020-03-16 02:01:41 +00:00
|
|
|
def __init__(self, rachio, config_entry):
|
2018-07-01 15:54:51 +00:00
|
|
|
"""Create an object from the provided API instance."""
|
|
|
|
# Use API token to get user ID
|
|
|
|
self.rachio = rachio
|
2020-03-14 05:46:17 +00:00
|
|
|
self.config_entry = config_entry
|
2020-03-16 02:01:41 +00:00
|
|
|
self.username = None
|
|
|
|
self._id = None
|
|
|
|
self._controllers = []
|
2018-07-01 15:54:51 +00:00
|
|
|
|
2020-03-16 02:01:41 +00:00
|
|
|
def setup(self, hass):
|
|
|
|
"""Rachio device setup."""
|
|
|
|
response = self.rachio.person.getInfo()
|
2018-07-01 15:54:51 +00:00
|
|
|
assert int(response[0][KEY_STATUS]) == 200, "API key error"
|
|
|
|
self._id = response[1][KEY_ID]
|
|
|
|
|
|
|
|
# Use user ID to get user data
|
2020-03-16 02:01:41 +00:00
|
|
|
data = self.rachio.person.get(self._id)
|
2018-07-01 15:54:51 +00:00
|
|
|
assert int(data[0][KEY_STATUS]) == 200, "User ID error"
|
|
|
|
self.username = data[1][KEY_USERNAME]
|
2020-03-13 21:38:14 +00:00
|
|
|
devices = data[1][KEY_DEVICES]
|
|
|
|
for controller in devices:
|
|
|
|
webhooks = self.rachio.notification.getDeviceWebhook(controller[KEY_ID])[1]
|
|
|
|
# The API does not provide a way to tell if a controller is shared
|
|
|
|
# or if they are the owner. To work around this problem we fetch the webooks
|
|
|
|
# before we setup the device so we can skip it instead of failing.
|
|
|
|
# webhooks are normally a list, however if there is an error
|
|
|
|
# rachio hands us back a dict
|
|
|
|
if isinstance(webhooks, dict):
|
|
|
|
_LOGGER.error(
|
|
|
|
"Failed to add rachio controller '%s' because of an error: %s",
|
|
|
|
controller[KEY_NAME],
|
|
|
|
webhooks.get("error", "Unknown Error"),
|
|
|
|
)
|
|
|
|
continue
|
2020-03-16 02:01:41 +00:00
|
|
|
|
|
|
|
rachio_iro = RachioIro(hass, self.rachio, controller, webhooks)
|
|
|
|
rachio_iro.setup()
|
|
|
|
self._controllers.append(rachio_iro)
|
2018-07-01 15:54:51 +00:00
|
|
|
_LOGGER.info('Using Rachio API as user "%s"', self.username)
|
|
|
|
|
|
|
|
@property
|
|
|
|
def user_id(self) -> str:
|
|
|
|
"""Get the user ID as defined by the Rachio API."""
|
|
|
|
return self._id
|
|
|
|
|
|
|
|
@property
|
|
|
|
def controllers(self) -> list:
|
|
|
|
"""Get a list of controllers managed by this account."""
|
|
|
|
return self._controllers
|
|
|
|
|
|
|
|
|
2018-07-20 08:45:20 +00:00
|
|
|
class RachioIro:
|
2018-07-01 15:54:51 +00:00
|
|
|
"""Represent a Rachio Iro."""
|
|
|
|
|
2020-03-13 21:38:14 +00:00
|
|
|
def __init__(self, hass, rachio, data, webhooks):
|
2018-07-01 15:54:51 +00:00
|
|
|
"""Initialize a Rachio device."""
|
|
|
|
self.hass = hass
|
|
|
|
self.rachio = rachio
|
|
|
|
self._id = data[KEY_ID]
|
2020-03-16 02:01:41 +00:00
|
|
|
self.name = data[KEY_NAME]
|
|
|
|
self.serial_number = data[KEY_SERIAL_NUMBER]
|
|
|
|
self.mac_address = data[KEY_MAC_ADDRESS]
|
|
|
|
self.model = data[KEY_MODEL]
|
2018-07-01 15:54:51 +00:00
|
|
|
self._zones = data[KEY_ZONES]
|
|
|
|
self._init_data = data
|
2020-03-13 21:38:14 +00:00
|
|
|
self._webhooks = webhooks
|
2018-07-01 15:54:51 +00:00
|
|
|
_LOGGER.debug('%s has ID "%s"', str(self), self.controller_id)
|
|
|
|
|
2020-03-16 02:01:41 +00:00
|
|
|
def setup(self):
|
|
|
|
"""Rachio Iro setup for webhooks."""
|
2018-07-01 15:54:51 +00:00
|
|
|
# Listen for all updates
|
|
|
|
self._init_webhooks()
|
|
|
|
|
|
|
|
def _init_webhooks(self) -> None:
|
|
|
|
"""Start getting updates from the Rachio API."""
|
|
|
|
current_webhook_id = None
|
|
|
|
|
|
|
|
# First delete any old webhooks that may have stuck around
|
|
|
|
def _deinit_webhooks(event) -> None:
|
|
|
|
"""Stop getting updates from the Rachio API."""
|
2020-03-13 21:38:14 +00:00
|
|
|
if not self._webhooks:
|
|
|
|
# We fetched webhooks when we created the device, however if we call _init_webhooks
|
|
|
|
# again we need to fetch again
|
|
|
|
self._webhooks = self.rachio.notification.getDeviceWebhook(
|
|
|
|
self.controller_id
|
|
|
|
)[1]
|
|
|
|
for webhook in self._webhooks:
|
2019-07-31 19:25:30 +00:00
|
|
|
if (
|
|
|
|
webhook[KEY_EXTERNAL_ID].startswith(WEBHOOK_CONST_ID)
|
|
|
|
or webhook[KEY_ID] == current_webhook_id
|
|
|
|
):
|
2018-07-01 15:54:51 +00:00
|
|
|
self.rachio.notification.deleteWebhook(webhook[KEY_ID])
|
2020-03-13 21:38:14 +00:00
|
|
|
self._webhooks = None
|
2019-07-31 19:25:30 +00:00
|
|
|
|
2018-07-01 15:54:51 +00:00
|
|
|
_deinit_webhooks(None)
|
|
|
|
|
|
|
|
# Choose which events to listen for and get their IDs
|
|
|
|
event_types = []
|
|
|
|
for event_type in self.rachio.notification.getWebhookEventType()[1]:
|
|
|
|
if event_type[KEY_NAME] in LISTEN_EVENT_TYPES:
|
|
|
|
event_types.append({"id": event_type[KEY_ID]})
|
|
|
|
|
|
|
|
# Register to listen to these events from the device
|
|
|
|
url = self.rachio.webhook_url
|
|
|
|
auth = WEBHOOK_CONST_ID + self.rachio.webhook_auth
|
2019-07-31 19:25:30 +00:00
|
|
|
new_webhook = self.rachio.notification.postWebhook(
|
|
|
|
self.controller_id, auth, url, event_types
|
|
|
|
)
|
2018-07-01 15:54:51 +00:00
|
|
|
# Save ID for deletion at shutdown
|
|
|
|
current_webhook_id = new_webhook[1][KEY_ID]
|
|
|
|
self.hass.bus.listen(EVENT_HOMEASSISTANT_STOP, _deinit_webhooks)
|
|
|
|
|
|
|
|
def __str__(self) -> str:
|
|
|
|
"""Display the controller as a string."""
|
2019-09-03 19:14:39 +00:00
|
|
|
return f'Rachio controller "{self.name}"'
|
2018-07-01 15:54:51 +00:00
|
|
|
|
|
|
|
@property
|
|
|
|
def controller_id(self) -> str:
|
|
|
|
"""Return the Rachio API controller ID."""
|
|
|
|
return self._id
|
|
|
|
|
|
|
|
@property
|
|
|
|
def current_schedule(self) -> str:
|
|
|
|
"""Return the schedule that the device is running right now."""
|
|
|
|
return self.rachio.device.getCurrentSchedule(self.controller_id)[1]
|
|
|
|
|
|
|
|
@property
|
|
|
|
def init_data(self) -> dict:
|
|
|
|
"""Return the information used to set up the controller."""
|
|
|
|
return self._init_data
|
|
|
|
|
|
|
|
def list_zones(self, include_disabled=False) -> list:
|
|
|
|
"""Return a list of the zone dicts connected to the device."""
|
|
|
|
# All zones
|
|
|
|
if include_disabled:
|
|
|
|
return self._zones
|
|
|
|
|
|
|
|
# Only enabled zones
|
|
|
|
return [z for z in self._zones if z[KEY_ENABLED]]
|
|
|
|
|
2018-07-23 08:24:39 +00:00
|
|
|
def get_zone(self, zone_id) -> Optional[dict]:
|
2018-07-01 15:54:51 +00:00
|
|
|
"""Return the zone with the given ID."""
|
|
|
|
for zone in self.list_zones(include_disabled=True):
|
|
|
|
if zone[KEY_ID] == zone_id:
|
|
|
|
return zone
|
|
|
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
def stop_watering(self) -> None:
|
|
|
|
"""Stop watering all zones connected to this controller."""
|
|
|
|
self.rachio.device.stopWater(self.controller_id)
|
|
|
|
_LOGGER.info("Stopped watering of all zones on %s", str(self))
|
|
|
|
|
|
|
|
|
2020-03-16 02:01:41 +00:00
|
|
|
class RachioDeviceInfoProvider(Entity):
|
|
|
|
"""Mixin to provide device_info."""
|
|
|
|
|
|
|
|
def __init__(self, controller):
|
|
|
|
"""Initialize a Rachio device."""
|
|
|
|
super().__init__()
|
|
|
|
self._controller = controller
|
|
|
|
|
|
|
|
@property
|
|
|
|
def device_info(self):
|
|
|
|
"""Return the device_info of the device."""
|
|
|
|
return {
|
|
|
|
"identifiers": {(DOMAIN, self._controller.serial_number,)},
|
|
|
|
"connections": {
|
|
|
|
(device_registry.CONNECTION_NETWORK_MAC, self._controller.mac_address,)
|
|
|
|
},
|
|
|
|
"name": self._controller.name,
|
|
|
|
"model": self._controller.model,
|
|
|
|
"manufacturer": DEFAULT_NAME,
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2018-07-01 15:54:51 +00:00
|
|
|
class RachioWebhookView(HomeAssistantView):
|
|
|
|
"""Provide a page for the server to call."""
|
|
|
|
|
|
|
|
SIGNALS = {
|
|
|
|
TYPE_CONTROLLER_STATUS: SIGNAL_RACHIO_CONTROLLER_UPDATE,
|
|
|
|
TYPE_SCHEDULE_STATUS: SIGNAL_RACHIO_SCHEDULE_UPDATE,
|
|
|
|
TYPE_ZONE_STATUS: SIGNAL_RACHIO_ZONE_UPDATE,
|
|
|
|
}
|
|
|
|
|
|
|
|
requires_auth = False # Handled separately
|
|
|
|
|
2020-03-14 05:46:17 +00:00
|
|
|
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("/", ":")
|
2020-03-16 02:01:41 +00:00
|
|
|
_LOGGER.debug(
|
|
|
|
"Initialize webhook at url: %s, with name %s", self.url, self.name
|
|
|
|
)
|
2020-03-14 05:46:17 +00:00
|
|
|
|
2018-07-01 15:54:51 +00:00
|
|
|
async def post(self, request) -> web.Response:
|
|
|
|
"""Handle webhook calls from the server."""
|
2019-07-31 19:25:30 +00:00
|
|
|
hass = request.app["hass"]
|
2018-07-01 15:54:51 +00:00
|
|
|
data = await request.json()
|
|
|
|
|
|
|
|
try:
|
2019-07-31 19:25:30 +00:00
|
|
|
auth = data.get(KEY_EXTERNAL_ID, str()).split(":")[1]
|
2020-03-14 05:46:17 +00:00
|
|
|
assert auth == hass.data[DOMAIN][self._entry_id].rachio.webhook_auth
|
2018-07-01 15:54:51 +00:00
|
|
|
except (AssertionError, IndexError):
|
|
|
|
return web.Response(status=web.HTTPForbidden.status_code)
|
|
|
|
|
|
|
|
update_type = data[KEY_TYPE]
|
|
|
|
if update_type in self.SIGNALS:
|
|
|
|
async_dispatcher_send(hass, self.SIGNALS[update_type], data)
|
|
|
|
|
|
|
|
return web.Response(status=web.HTTPNoContent.status_code)
|