Home Assistant Unofficial Reference 2024.12.1
webhooks.py
Go to the documentation of this file.
1 """Webhooks used by rachio."""
2 
3 from __future__ import annotations
4 
5 from aiohttp import web
6 
7 from homeassistant.components import cloud, webhook
8 from homeassistant.config_entries import ConfigEntry
9 from homeassistant.const import CONF_WEBHOOK_ID, URL_API
10 from homeassistant.core import HomeAssistant, callback
11 from homeassistant.helpers.dispatcher import async_dispatcher_send
12 
13 from .const import (
14  CONF_CLOUDHOOK_URL,
15  DOMAIN,
16  KEY_EXTERNAL_ID,
17  KEY_TYPE,
18  SIGNAL_RACHIO_CONTROLLER_UPDATE,
19  SIGNAL_RACHIO_RAIN_DELAY_UPDATE,
20  SIGNAL_RACHIO_RAIN_SENSOR_UPDATE,
21  SIGNAL_RACHIO_SCHEDULE_UPDATE,
22  SIGNAL_RACHIO_ZONE_UPDATE,
23 )
24 from .device import RachioPerson
25 
26 # Device webhook values
27 TYPE_CONTROLLER_STATUS = "DEVICE_STATUS"
28 SUBTYPE_OFFLINE = "OFFLINE"
29 SUBTYPE_ONLINE = "ONLINE"
30 SUBTYPE_OFFLINE_NOTIFICATION = "OFFLINE_NOTIFICATION"
31 SUBTYPE_COLD_REBOOT = "COLD_REBOOT"
32 SUBTYPE_SLEEP_MODE_ON = "SLEEP_MODE_ON"
33 SUBTYPE_SLEEP_MODE_OFF = "SLEEP_MODE_OFF"
34 SUBTYPE_BROWNOUT_VALVE = "BROWNOUT_VALVE"
35 
36 # Rain delay values
37 TYPE_RAIN_DELAY_STATUS = "RAIN_DELAY"
38 SUBTYPE_RAIN_DELAY_ON = "RAIN_DELAY_ON"
39 SUBTYPE_RAIN_DELAY_OFF = "RAIN_DELAY_OFF"
40 
41 # Rain sensor values
42 TYPE_RAIN_SENSOR_STATUS = "RAIN_SENSOR_DETECTION"
43 SUBTYPE_RAIN_SENSOR_DETECTION_ON = "RAIN_SENSOR_DETECTION_ON"
44 SUBTYPE_RAIN_SENSOR_DETECTION_OFF = "RAIN_SENSOR_DETECTION_OFF"
45 
46 # Schedule webhook values
47 TYPE_SCHEDULE_STATUS = "SCHEDULE_STATUS"
48 SUBTYPE_SCHEDULE_STARTED = "SCHEDULE_STARTED"
49 SUBTYPE_SCHEDULE_STOPPED = "SCHEDULE_STOPPED"
50 SUBTYPE_SCHEDULE_COMPLETED = "SCHEDULE_COMPLETED"
51 SUBTYPE_WEATHER_NO_SKIP = "WEATHER_INTELLIGENCE_NO_SKIP"
52 SUBTYPE_WEATHER_SKIP = "WEATHER_INTELLIGENCE_SKIP"
53 SUBTYPE_WEATHER_CLIMATE_SKIP = "WEATHER_INTELLIGENCE_CLIMATE_SKIP"
54 SUBTYPE_WEATHER_FREEZE = "WEATHER_INTELLIGENCE_FREEZE"
55 
56 # Zone webhook values
57 TYPE_ZONE_STATUS = "ZONE_STATUS"
58 SUBTYPE_ZONE_STARTED = "ZONE_STARTED"
59 SUBTYPE_ZONE_STOPPED = "ZONE_STOPPED"
60 SUBTYPE_ZONE_COMPLETED = "ZONE_COMPLETED"
61 SUBTYPE_ZONE_CYCLING = "ZONE_CYCLING"
62 SUBTYPE_ZONE_CYCLING_COMPLETED = "ZONE_CYCLING_COMPLETED"
63 SUBTYPE_ZONE_PAUSED = "ZONE_PAUSED"
64 
65 # Webhook callbacks
66 LISTEN_EVENT_TYPES = [
67  "DEVICE_STATUS_EVENT",
68  "ZONE_STATUS_EVENT",
69  "RAIN_DELAY_EVENT",
70  "RAIN_SENSOR_DETECTION_EVENT",
71  "SCHEDULE_STATUS_EVENT",
72 ]
73 WEBHOOK_CONST_ID = "homeassistant.rachio:"
74 WEBHOOK_PATH = URL_API + DOMAIN
75 
76 SIGNAL_MAP = {
77  TYPE_CONTROLLER_STATUS: SIGNAL_RACHIO_CONTROLLER_UPDATE,
78  TYPE_RAIN_DELAY_STATUS: SIGNAL_RACHIO_RAIN_DELAY_UPDATE,
79  TYPE_RAIN_SENSOR_STATUS: SIGNAL_RACHIO_RAIN_SENSOR_UPDATE,
80  TYPE_SCHEDULE_STATUS: SIGNAL_RACHIO_SCHEDULE_UPDATE,
81  TYPE_ZONE_STATUS: SIGNAL_RACHIO_ZONE_UPDATE,
82 }
83 
84 
85 @callback
86 def async_register_webhook(hass: HomeAssistant, entry: ConfigEntry) -> None:
87  """Register a webhook."""
88  webhook_id: str = entry.data[CONF_WEBHOOK_ID]
89 
90  async def _async_handle_rachio_webhook(
91  hass: HomeAssistant, webhook_id: str, request: web.Request
92  ) -> web.Response:
93  """Handle webhook calls from the server."""
94  person: RachioPerson = hass.data[DOMAIN][entry.entry_id]
95  data = await request.json()
96 
97  try:
98  assert (
99  data.get(KEY_EXTERNAL_ID, "").split(":")[1]
100  == person.rachio.webhook_auth
101  )
102  except (AssertionError, IndexError):
103  return web.Response(status=web.HTTPForbidden.status_code)
104 
105  update_type = data[KEY_TYPE]
106  if update_type in SIGNAL_MAP:
107  async_dispatcher_send(hass, SIGNAL_MAP[update_type], data)
108 
109  return web.Response(status=web.HTTPNoContent.status_code)
110 
111  webhook.async_register(
112  hass, DOMAIN, "Rachio", webhook_id, _async_handle_rachio_webhook
113  )
114 
115 
116 @callback
117 def async_unregister_webhook(hass: HomeAssistant, entry: ConfigEntry) -> None:
118  """Unregister a webhook."""
119  webhook_id: str = entry.data[CONF_WEBHOOK_ID]
120  webhook.async_unregister(hass, webhook_id)
121 
122 
124  hass: HomeAssistant, entry: ConfigEntry
125 ) -> str:
126  """Generate webhook url."""
127  config = entry.data.copy()
128 
129  updated_config = False
130  webhook_url = None
131 
132  if not (webhook_id := config.get(CONF_WEBHOOK_ID)):
133  webhook_id = webhook.async_generate_id()
134  config[CONF_WEBHOOK_ID] = webhook_id
135  updated_config = True
136 
137  if cloud.async_active_subscription(hass):
138  if not (cloudhook_url := config.get(CONF_CLOUDHOOK_URL)):
139  cloudhook_url = await cloud.async_create_cloudhook(hass, webhook_id)
140  config[CONF_CLOUDHOOK_URL] = cloudhook_url
141  updated_config = True
142  webhook_url = cloudhook_url
143 
144  if not webhook_url:
145  webhook_url = webhook.async_generate_url(hass, webhook_id)
146 
147  if updated_config:
148  hass.config_entries.async_update_entry(entry, data=config)
149 
150  return webhook_url
None async_register_webhook(HomeAssistant hass, ConfigEntry entry)
Definition: webhooks.py:86
None async_unregister_webhook(HomeAssistant hass, ConfigEntry entry)
Definition: webhooks.py:117
str async_get_or_create_registered_webhook_id_and_url(HomeAssistant hass, ConfigEntry entry)
Definition: webhooks.py:125
None async_dispatcher_send(HomeAssistant hass, str signal, *Any args)
Definition: dispatcher.py:193