Home Assistant Unofficial Reference 2024.12.1
calendar.py
Go to the documentation of this file.
1 """Support for WebDav Calendar."""
2 
3 from __future__ import annotations
4 
5 from datetime import datetime
6 import logging
7 
8 import caldav
9 import voluptuous as vol
10 
12  ENTITY_ID_FORMAT,
13  PLATFORM_SCHEMA as CALENDAR_PLATFORM_SCHEMA,
14  CalendarEntity,
15  CalendarEvent,
16  is_offset_reached,
17 )
18 from homeassistant.const import (
19  CONF_NAME,
20  CONF_PASSWORD,
21  CONF_URL,
22  CONF_USERNAME,
23  CONF_VERIFY_SSL,
24 )
25 from homeassistant.core import HomeAssistant, callback
27 from homeassistant.helpers.entity import async_generate_entity_id
28 from homeassistant.helpers.entity_platform import AddEntitiesCallback
29 from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
30 from homeassistant.helpers.update_coordinator import CoordinatorEntity
31 
32 from . import CalDavConfigEntry
33 from .api import async_get_calendars
34 from .coordinator import CalDavUpdateCoordinator
35 
36 _LOGGER = logging.getLogger(__name__)
37 
38 CONF_CALENDARS = "calendars"
39 CONF_CUSTOM_CALENDARS = "custom_calendars"
40 CONF_CALENDAR = "calendar"
41 CONF_SEARCH = "search"
42 CONF_DAYS = "days"
43 
44 # Number of days to look ahead for next event when configured by ConfigEntry
45 CONFIG_ENTRY_DEFAULT_DAYS = 7
46 
47 # Only allow VCALENDARs that support this component type
48 SUPPORTED_COMPONENT = "VEVENT"
49 
50 PLATFORM_SCHEMA = CALENDAR_PLATFORM_SCHEMA.extend(
51  {
52  vol.Required(CONF_URL): vol.Url(),
53  vol.Optional(CONF_CALENDARS, default=[]): vol.All(cv.ensure_list, [cv.string]),
54  vol.Inclusive(CONF_USERNAME, "authentication"): cv.string,
55  vol.Inclusive(CONF_PASSWORD, "authentication"): cv.string,
56  vol.Optional(CONF_CUSTOM_CALENDARS, default=[]): vol.All(
57  cv.ensure_list,
58  [
59  vol.Schema(
60  {
61  vol.Required(CONF_CALENDAR): cv.string,
62  vol.Required(CONF_NAME): cv.string,
63  vol.Required(CONF_SEARCH): cv.string,
64  }
65  )
66  ],
67  ),
68  vol.Optional(CONF_VERIFY_SSL, default=True): cv.boolean,
69  vol.Optional(CONF_DAYS, default=1): cv.positive_int,
70  }
71 )
72 
73 
75  hass: HomeAssistant,
76  config: ConfigType,
77  async_add_entities: AddEntitiesCallback,
78  disc_info: DiscoveryInfoType | None = None,
79 ) -> None:
80  """Set up the WebDav Calendar platform."""
81  url = config[CONF_URL]
82  username = config.get(CONF_USERNAME)
83  password = config.get(CONF_PASSWORD)
84  days = config[CONF_DAYS]
85 
86  client = caldav.DAVClient(
87  url, None, username, password, ssl_verify_cert=config[CONF_VERIFY_SSL]
88  )
89 
90  calendars = await async_get_calendars(hass, client, SUPPORTED_COMPONENT)
91 
92  entities = []
93  device_id: str | None
94  for calendar in list(calendars):
95  # If a calendar name was given in the configuration,
96  # ignore all the others
97  if config[CONF_CALENDARS] and calendar.name not in config[CONF_CALENDARS]:
98  _LOGGER.debug("Ignoring calendar '%s'", calendar.name)
99  continue
100 
101  # Create additional calendars based on custom filtering rules
102  for cust_calendar in config[CONF_CUSTOM_CALENDARS]:
103  # Check that the base calendar matches
104  if cust_calendar[CONF_CALENDAR] != calendar.name:
105  continue
106 
107  name = cust_calendar[CONF_NAME]
108  device_id = f"{cust_calendar[CONF_CALENDAR]} {cust_calendar[CONF_NAME]}"
109  entity_id = async_generate_entity_id(ENTITY_ID_FORMAT, device_id, hass=hass)
110  coordinator = CalDavUpdateCoordinator(
111  hass,
112  None,
113  calendar=calendar,
114  days=days,
115  include_all_day=True,
116  search=cust_calendar[CONF_SEARCH],
117  )
118  entities.append(
119  WebDavCalendarEntity(name, entity_id, coordinator, supports_offset=True)
120  )
121 
122  # Create a default calendar if there was no custom one for all calendars
123  # that support events.
124  if not config[CONF_CUSTOM_CALENDARS]:
125  name = calendar.name
126  device_id = calendar.name
127  entity_id = async_generate_entity_id(ENTITY_ID_FORMAT, device_id, hass=hass)
128  coordinator = CalDavUpdateCoordinator(
129  hass,
130  None,
131  calendar=calendar,
132  days=days,
133  include_all_day=False,
134  search=None,
135  )
136  entities.append(
137  WebDavCalendarEntity(name, entity_id, coordinator, supports_offset=True)
138  )
139 
140  async_add_entities(entities, True)
141 
142 
144  hass: HomeAssistant,
145  entry: CalDavConfigEntry,
146  async_add_entities: AddEntitiesCallback,
147 ) -> None:
148  """Set up the CalDav calendar platform for a config entry."""
149  calendars = await async_get_calendars(hass, entry.runtime_data, SUPPORTED_COMPONENT)
151  (
153  calendar.name,
154  async_generate_entity_id(ENTITY_ID_FORMAT, calendar.name, hass=hass),
156  hass,
157  entry,
158  calendar=calendar,
159  days=CONFIG_ENTRY_DEFAULT_DAYS,
160  include_all_day=True,
161  search=None,
162  ),
163  unique_id=f"{entry.entry_id}-{calendar.id}",
164  )
165  for calendar in calendars
166  if calendar.name
167  ),
168  True,
169  )
170 
171 
172 class WebDavCalendarEntity(CoordinatorEntity[CalDavUpdateCoordinator], CalendarEntity):
173  """A device for getting the next Task from a WebDav Calendar."""
174 
175  def __init__(
176  self,
177  name: str,
178  entity_id: str,
179  coordinator: CalDavUpdateCoordinator,
180  unique_id: str | None = None,
181  supports_offset: bool = False,
182  ) -> None:
183  """Create the WebDav Calendar Event Device."""
184  super().__init__(coordinator)
185  self.entity_identity_identity_id = entity_id
186  self._event_event: CalendarEvent | None = None
187  self._attr_name_attr_name = name
188  if unique_id is not None:
189  self._attr_unique_id_attr_unique_id = unique_id
190  self._supports_offset_supports_offset = supports_offset
191 
192  @property
193  def event(self) -> CalendarEvent | None:
194  """Return the next upcoming event."""
195  return self._event_event
196 
197  async def async_get_events(
198  self, hass: HomeAssistant, start_date: datetime, end_date: datetime
199  ) -> list[CalendarEvent]:
200  """Get all events in a specific time frame."""
201  return await self.coordinator.async_get_events(hass, start_date, end_date)
202 
203  @callback
204  def _handle_coordinator_update(self) -> None:
205  """Update event data."""
206  self._event_event = self.coordinator.data
207  if self._supports_offset_supports_offset:
208  self._attr_extra_state_attributes_attr_extra_state_attributes = {
209  "offset_reached": is_offset_reached(
210  self._event_event.start_datetime_local,
211  self.coordinator.offset, # type: ignore[arg-type]
212  )
213  if self._event_event
214  else False
215  }
217 
218  async def async_added_to_hass(self) -> None:
219  """When entity is added to hass update state from existing coordinator data."""
220  await super().async_added_to_hass()
221  self._handle_coordinator_update_handle_coordinator_update()
None __init__(self, str name, str entity_id, CalDavUpdateCoordinator coordinator, str|None unique_id=None, bool supports_offset=False)
Definition: calendar.py:182
list[CalendarEvent] async_get_events(self, HomeAssistant hass, datetime start_date, datetime end_date)
Definition: calendar.py:199
list[caldav.Calendar] async_get_calendars(HomeAssistant hass, caldav.DAVClient client, str component)
Definition: api.py:10
None async_setup_platform(HomeAssistant hass, ConfigType config, AddEntitiesCallback async_add_entities, DiscoveryInfoType|None disc_info=None)
Definition: calendar.py:79
None async_setup_entry(HomeAssistant hass, CalDavConfigEntry entry, AddEntitiesCallback async_add_entities)
Definition: calendar.py:147
bool is_offset_reached(datetime.datetime start, datetime.timedelta offset_time)
Definition: __init__.py:479
str async_generate_entity_id(str entity_id_format, str|None name, Iterable[str]|None current_ids=None, HomeAssistant|None hass=None)
Definition: entity.py:119