Home Assistant Unofficial Reference 2024.12.1
calendar.py
Go to the documentation of this file.
1 """Rachio smart hose timer calendar."""
2 
3 from datetime import datetime, timedelta
4 import logging
5 from typing import Any
6 
8  CalendarEntity,
9  CalendarEntityFeature,
10  CalendarEvent,
11 )
12 from homeassistant.config_entries import ConfigEntry
13 from homeassistant.core import HomeAssistant
14 from homeassistant.exceptions import HomeAssistantError
15 from homeassistant.helpers.entity_platform import AddEntitiesCallback
16 from homeassistant.helpers.update_coordinator import CoordinatorEntity
17 from homeassistant.util import dt as dt_util
18 
19 from .const import (
20  DOMAIN as DOMAIN_RACHIO,
21  KEY_ADDRESS,
22  KEY_DURATION_SECONDS,
23  KEY_ID,
24  KEY_LOCALITY,
25  KEY_PROGRAM_ID,
26  KEY_PROGRAM_NAME,
27  KEY_RUN_SUMMARIES,
28  KEY_SERIAL_NUMBER,
29  KEY_SKIP,
30  KEY_SKIPPABLE,
31  KEY_START_TIME,
32  KEY_TOTAL_RUN_DURATION,
33  KEY_VALVE_NAME,
34 )
35 from .coordinator import RachioScheduleUpdateCoordinator
36 from .device import RachioPerson
37 
38 _LOGGER = logging.getLogger(__name__)
39 
40 
42  hass: HomeAssistant,
43  config_entry: ConfigEntry,
44  async_add_entities: AddEntitiesCallback,
45 ) -> None:
46  """Set up entry for Rachio smart hose timer calendar."""
47  person: RachioPerson = hass.data[DOMAIN_RACHIO][config_entry.entry_id]
49  RachioCalendarEntity(base_station.schedule_coordinator, base_station)
50  for base_station in person.base_stations
51  )
52 
53 
55  CoordinatorEntity[RachioScheduleUpdateCoordinator], CalendarEntity
56 ):
57  """Rachio calendar entity."""
58 
59  _attr_has_entity_name = True
60  _attr_translation_key = "calendar"
61  _attr_supported_features = CalendarEntityFeature.DELETE_EVENT
62 
63  def __init__(
64  self, coordinator: RachioScheduleUpdateCoordinator, base_station
65  ) -> None:
66  """Initialize a Rachio calendar entity."""
67  super().__init__(coordinator)
68  self.base_stationbase_stationbase_station = base_station
69  self._event: CalendarEvent | None = None
70  self._location_location = coordinator.base_station[KEY_ADDRESS][KEY_LOCALITY]
71  self._attr_translation_placeholders_attr_translation_placeholders = {
72  "base": coordinator.base_station[KEY_SERIAL_NUMBER]
73  }
74  self._attr_unique_id_attr_unique_id = f"{coordinator.base_station[KEY_ID]}-calendar"
75  self._previous_event_previous_event: dict[str, Any] | None = None
76 
77  @property
78  def event(self) -> CalendarEvent | None:
79  """Return the next upcoming event."""
80  if not (event := self._handle_upcoming_event_handle_upcoming_event()):
81  return None
82  start_time = dt_util.parse_datetime(event[KEY_START_TIME], raise_on_error=True)
83  valves = ", ".join(
84  [event[KEY_VALVE_NAME] for event in event[KEY_RUN_SUMMARIES]]
85  )
86  return CalendarEvent(
87  summary=event[KEY_PROGRAM_NAME],
88  start=dt_util.as_local(start_time),
89  end=dt_util.as_local(start_time)
90  + timedelta(seconds=int(event[KEY_TOTAL_RUN_DURATION])),
91  description=valves,
92  location=self._location_location,
93  )
94 
95  def _handle_upcoming_event(self) -> dict[str, Any] | None:
96  """Handle current or next event."""
97  # Currently when an event starts, it disappears from the
98  # API until the event ends. So we store the upcoming event and use
99  # the stored version if it's within the event time window.
100  if self._previous_event_previous_event:
101  start_time = dt_util.parse_datetime(
102  self._previous_event_previous_event[KEY_START_TIME], raise_on_error=True
103  )
104  end_time = start_time + timedelta(
105  seconds=int(self._previous_event_previous_event[KEY_TOTAL_RUN_DURATION])
106  )
107  if start_time <= dt_util.now() <= end_time:
108  return self._previous_event_previous_event
109 
110  schedule = iter(self.coordinator.data)
111  event = next(schedule, None)
112  if not event: # Schedule is empty
113  return None
114  while (
115  not event[KEY_SKIPPABLE] or KEY_SKIP in event[KEY_RUN_SUMMARIES][0]
116  ): # Not being skippable indicates the event is in the past
117  event = next(schedule, None)
118  if not event: # Schedule only has past or skipped events
119  return None
120  self._previous_event_previous_event = event # Store for future use
121  return event
122 
123  async def async_get_events(
124  self, hass: HomeAssistant, start_date: datetime, end_date: datetime
125  ) -> list[CalendarEvent]:
126  """Get all events in a specific time frame."""
127  if not self.coordinator.data:
128  raise HomeAssistantError("No events scheduled")
129  schedule = self.coordinator.data
130  event_list: list[CalendarEvent] = []
131 
132  for run in schedule:
133  event_start = dt_util.as_local(
134  dt_util.parse_datetime(run[KEY_START_TIME], raise_on_error=True)
135  )
136  if event_start > end_date:
137  break
138  if run[KEY_SKIPPABLE]: # Future events
139  event_end = event_start + timedelta(
140  seconds=int(run[KEY_TOTAL_RUN_DURATION])
141  )
142  else: # Past events
143  event_end = event_start + timedelta(
144  seconds=int(run[KEY_RUN_SUMMARIES][0][KEY_DURATION_SECONDS])
145  )
146 
147  if (
148  event_end > start_date
149  and event_start < end_date
150  and KEY_SKIP not in run[KEY_RUN_SUMMARIES][0]
151  ):
152  valves = ", ".join(
153  [event[KEY_VALVE_NAME] for event in run[KEY_RUN_SUMMARIES]]
154  )
155  event = CalendarEvent(
156  summary=run[KEY_PROGRAM_NAME],
157  start=event_start,
158  end=event_end,
159  description=valves,
160  location=self._location_location,
161  uid=f"{run[KEY_PROGRAM_ID]}/{run[KEY_START_TIME]}",
162  )
163  event_list.append(event)
164  return event_list
165 
167  self,
168  uid: str,
169  recurrence_id: str | None = None,
170  recurrence_range: str | None = None,
171  ) -> None:
172  """Skip an upcoming event on the calendar."""
173  program, timestamp = uid.split("/")
174  await self.hasshasshasshass.async_add_executor_job(
175  self.base_stationbase_stationbase_station.create_skip, program, timestamp
176  )
177  await self.coordinator.async_refresh()
list[CalendarEvent] async_get_events(self, HomeAssistant hass, datetime start_date, datetime end_date)
Definition: calendar.py:125
None __init__(self, RachioScheduleUpdateCoordinator coordinator, base_station)
Definition: calendar.py:65
None async_delete_event(self, str uid, str|None recurrence_id=None, str|None recurrence_range=None)
Definition: calendar.py:171
None async_setup_entry(HomeAssistant hass, ConfigEntry config_entry, AddEntitiesCallback async_add_entities)
Definition: calendar.py:45