Home Assistant Unofficial Reference 2024.12.1
sensor.py
Go to the documentation of this file.
1 """Support for showing the date and the time."""
2 
3 from __future__ import annotations
4 
5 from collections.abc import Callable, Mapping
6 from datetime import datetime, timedelta
7 import logging
8 from typing import Any
9 
10 import voluptuous as vol
11 
13  ENTITY_ID_FORMAT,
14  PLATFORM_SCHEMA as SENSOR_PLATFORM_SCHEMA,
15  SensorEntity,
16 )
17 from homeassistant.config_entries import ConfigEntry
18 from homeassistant.const import CONF_DISPLAY_OPTIONS, EVENT_CORE_CONFIG_UPDATE
19 from homeassistant.core import CALLBACK_TYPE, Event, HomeAssistant, callback
21 from homeassistant.helpers.entity_platform import AddEntitiesCallback
22 from homeassistant.helpers.event import async_track_point_in_utc_time
23 from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
24 import homeassistant.util.dt as dt_util
25 
26 from .const import OPTION_TYPES
27 
28 _LOGGER = logging.getLogger(__name__)
29 
30 TIME_STR_FORMAT = "%H:%M"
31 
32 
33 PLATFORM_SCHEMA = SENSOR_PLATFORM_SCHEMA.extend(
34  {
35  vol.Optional(CONF_DISPLAY_OPTIONS, default=["time"]): vol.All(
36  cv.ensure_list, [vol.In(OPTION_TYPES)]
37  )
38  }
39 )
40 
41 
43  hass: HomeAssistant,
44  config: ConfigType,
45  async_add_entities: AddEntitiesCallback,
46  discovery_info: DiscoveryInfoType | None = None,
47 ) -> None:
48  """Set up the Time and Date sensor."""
49  if hass.config.time_zone is None:
50  _LOGGER.error("Timezone is not set in Home Assistant configuration") # type: ignore[unreachable]
51  return
52 
54  [TimeDateSensor(variable) for variable in config[CONF_DISPLAY_OPTIONS]]
55  )
56 
57 
59  hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
60 ) -> None:
61  """Set up the Time & Date sensor."""
62 
64  [TimeDateSensor(entry.options[CONF_DISPLAY_OPTIONS], entry.entry_id)]
65  )
66 
67 
69  """Implementation of a Time and Date sensor."""
70 
71  _attr_should_poll = False
72  _attr_has_entity_name = True
73  _state: str | None = None
74  unsub: CALLBACK_TYPE | None = None
75 
76  def __init__(self, option_type: str, entry_id: str | None = None) -> None:
77  """Initialize the sensor."""
78  self._attr_translation_key_attr_translation_key = option_type
79  self.typetype = option_type
80  self.entity_identity_identity_id = ENTITY_ID_FORMAT.format(option_type)
81  self._attr_unique_id_attr_unique_id = option_type if entry_id else None
82 
83  self._update_internal_state_update_internal_state(dt_util.utcnow())
84 
85  @property
86  def native_value(self) -> str | None:
87  """Return the state of the sensor."""
88  return self._state_state
89 
90  @property
91  def icon(self) -> str:
92  """Icon to use in the frontend, if any."""
93  if "date" in self.typetype and "time" in self.typetype:
94  return "mdi:calendar-clock"
95  if "date" in self.typetype:
96  return "mdi:calendar"
97  return "mdi:clock"
98 
99  @callback
101  self,
102  preview_callback: Callable[[str, Mapping[str, Any]], None],
103  ) -> CALLBACK_TYPE:
104  """Render a preview."""
105 
106  @callback
107  def point_in_time_listener(time_date: datetime | None) -> None:
108  """Update preview."""
109 
110  now = dt_util.utcnow()
111  self._update_internal_state_update_internal_state(now)
113  self.hasshass, point_in_time_listener, self.get_next_intervalget_next_interval(now)
114  )
115  calculated_state = self._async_calculate_state_async_calculate_state()
116  preview_callback(calculated_state.state, calculated_state.attributes)
117 
118  @callback
119  def async_stop_preview() -> None:
120  """Stop preview."""
121  if self.unsubunsub:
122  self.unsubunsub()
123  self.unsubunsub = None
124 
126  return async_stop_preview
127 
128  async def async_added_to_hass(self) -> None:
129  """Set up first update."""
130 
131  async def async_update_config(event: Event) -> None:
132  """Handle core config update."""
133  self._update_state_and_setup_listener_update_state_and_setup_listener()
134  self.async_write_ha_stateasync_write_ha_state()
135 
136  self.async_on_removeasync_on_remove(
137  self.hasshass.bus.async_listen(EVENT_CORE_CONFIG_UPDATE, async_update_config)
138  )
139  self._update_state_and_setup_listener_update_state_and_setup_listener()
140 
141  async def async_will_remove_from_hass(self) -> None:
142  """Cancel next update."""
143  if self.unsubunsub:
144  self.unsubunsub()
145  self.unsubunsub = None
146 
147  def get_next_interval(self, time_date: datetime) -> datetime:
148  """Compute next time an update should occur."""
149  if self.typetype == "date":
150  tomorrow = dt_util.as_local(time_date) + timedelta(days=1)
151  return dt_util.start_of_local_day(tomorrow)
152 
153  timestamp = dt_util.as_timestamp(time_date)
154  interval = 60
155 
156  delta = interval - (timestamp % interval)
157  next_interval = time_date + timedelta(seconds=delta)
158  _LOGGER.debug("%s + %s -> %s (%s)", time_date, delta, next_interval, self.typetype)
159 
160  return next_interval
161 
162  def _update_internal_state(self, time_date: datetime) -> None:
163  time = dt_util.as_local(time_date).strftime(TIME_STR_FORMAT)
164  time_utc = time_date.strftime(TIME_STR_FORMAT)
165  date = dt_util.as_local(time_date).date().isoformat()
166  date_utc = time_date.date().isoformat()
167 
168  if self.typetype == "time":
169  self._state_state = time
170  elif self.typetype == "date":
171  self._state_state = date
172  elif self.typetype == "date_time":
173  self._state_state = f"{date}, {time}"
174  elif self.typetype == "date_time_utc":
175  self._state_state = f"{date_utc}, {time_utc}"
176  elif self.typetype == "time_date":
177  self._state_state = f"{time}, {date}"
178  elif self.typetype == "time_utc":
179  self._state_state = time_utc
180  elif self.typetype == "date_time_iso":
181  self._state_state = dt_util.parse_datetime(
182  f"{date} {time}", raise_on_error=True
183  ).isoformat()
184 
186  """Update state and setup listener for next interval."""
187  now = dt_util.utcnow()
188  self._update_internal_state_update_internal_state(now)
190  self.hasshass, self.point_in_time_listenerpoint_in_time_listener, self.get_next_intervalget_next_interval(now)
191  )
192 
193  @callback
194  def point_in_time_listener(self, time_date: datetime) -> None:
195  """Get the latest data and update state."""
196  self._update_state_and_setup_listener_update_state_and_setup_listener()
197  self.async_write_ha_stateasync_write_ha_state()
None __init__(self, str option_type, str|None entry_id=None)
Definition: sensor.py:76
CALLBACK_TYPE async_start_preview(self, Callable[[str, Mapping[str, Any]], None] preview_callback)
Definition: sensor.py:103
None _update_internal_state(self, datetime time_date)
Definition: sensor.py:162
None point_in_time_listener(self, datetime time_date)
Definition: sensor.py:194
datetime get_next_interval(self, datetime time_date)
Definition: sensor.py:147
CalculatedState _async_calculate_state(self)
Definition: entity.py:1059
None async_on_remove(self, CALLBACK_TYPE func)
Definition: entity.py:1331
None async_setup_entry(HomeAssistant hass, ConfigEntry entry, AddEntitiesCallback async_add_entities)
Definition: sensor.py:60
None async_setup_platform(HomeAssistant hass, ConfigType config, AddEntitiesCallback async_add_entities, DiscoveryInfoType|None discovery_info=None)
Definition: sensor.py:47
CALLBACK_TYPE async_track_point_in_utc_time(HomeAssistant hass, HassJob[[datetime], Coroutine[Any, Any, None]|None]|Callable[[datetime], Coroutine[Any, Any, None]|None] action, datetime point_in_time)
Definition: event.py:1542