1 """Support for functionality to keep track of the sun."""
3 from __future__
import annotations
5 from datetime
import datetime, timedelta
9 from astral.location
import Elevation, Location
13 EVENT_CORE_CONFIG_UPDATE,
23 get_location_astral_event_next,
28 SIGNAL_EVENTS_CHANGED,
29 SIGNAL_POSITION_CHANGED,
34 type SunConfigEntry = ConfigEntry[Sun]
36 _LOGGER = logging.getLogger(__name__)
40 STATE_ATTR_AZIMUTH =
"azimuth"
41 STATE_ATTR_ELEVATION =
"elevation"
42 STATE_ATTR_RISING =
"rising"
43 STATE_ATTR_NEXT_DAWN =
"next_dawn"
44 STATE_ATTR_NEXT_DUSK =
"next_dusk"
45 STATE_ATTR_NEXT_MIDNIGHT =
"next_midnight"
46 STATE_ATTR_NEXT_NOON =
"next_noon"
47 STATE_ATTR_NEXT_RISING =
"next_rising"
48 STATE_ATTR_NEXT_SETTING =
"next_setting"
61 PHASE_ASTRONOMICAL_TWILIGHT =
"astronomical_twilight"
63 PHASE_NAUTICAL_TWILIGHT =
"nautical_twilight"
65 PHASE_TWILIGHT =
"twilight"
67 PHASE_SMALL_DAY =
"small_day"
76 PHASE_ASTRONOMICAL_TWILIGHT:
timedelta(minutes=4 * 2),
77 PHASE_NAUTICAL_TWILIGHT:
timedelta(minutes=4 * 2),
85 """Representation of the Sun."""
87 _unrecorded_attributes = frozenset(
94 STATE_ATTR_NEXT_MIDNIGHT,
96 STATE_ATTR_NEXT_RISING,
97 STATE_ATTR_NEXT_SETTING,
102 entity_id = ENTITY_ID
105 _no_platform_reported =
True
109 next_rising: datetime
110 next_setting: datetime
113 next_midnight: datetime
115 solar_elevation: float
118 _next_change: datetime
121 """Initialize the sun."""
123 self.
phasephase: str |
None =
None
128 "unrecorded_attributes": self._Entity__combined_unrecorded_attributes
141 """Update location."""
143 if not initial
and location == self.
locationlocation:
153 """Remove listeners."""
163 """Return the state of the sun."""
166 return STATE_ABOVE_HORIZON
168 return STATE_BELOW_HORIZON
172 """Return the state attributes of the sun."""
174 STATE_ATTR_NEXT_DAWN: self.
next_dawnnext_dawn.isoformat(),
175 STATE_ATTR_NEXT_DUSK: self.
next_dusknext_dusk.isoformat(),
176 STATE_ATTR_NEXT_MIDNIGHT: self.
next_midnightnext_midnight.isoformat(),
177 STATE_ATTR_NEXT_NOON: self.
next_noonnext_noon.isoformat(),
178 STATE_ATTR_NEXT_RISING: self.
next_risingnext_rising.isoformat(),
179 STATE_ATTR_NEXT_SETTING: self.
next_settingnext_setting.isoformat(),
182 STATE_ATTR_RISING: self.
risingrising,
186 self, utc_point_in_time: datetime, sun_event: str, before: str |
None
198 """Update the attributes containing solar events."""
200 utc_point_in_time = dt_util.utcnow()
205 self.
locationlocation.solar_depression =
"astronomical"
206 self.
_check_event_check_event(utc_point_in_time,
"dawn", PHASE_NIGHT)
207 self.
locationlocation.solar_depression =
"nautical"
208 self.
_check_event_check_event(utc_point_in_time,
"dawn", PHASE_ASTRONOMICAL_TWILIGHT)
209 self.
locationlocation.solar_depression =
"civil"
211 utc_point_in_time,
"dawn", PHASE_NAUTICAL_TWILIGHT
214 utc_point_in_time, SUN_EVENT_SUNRISE, PHASE_TWILIGHT
216 self.
locationlocation.solar_depression = -10
217 self.
_check_event_check_event(utc_point_in_time,
"dawn", PHASE_SMALL_DAY)
219 self.
_check_event_check_event(utc_point_in_time,
"dusk", PHASE_DAY)
221 utc_point_in_time, SUN_EVENT_SUNSET, PHASE_SMALL_DAY
223 self.
locationlocation.solar_depression =
"civil"
225 self.
locationlocation.solar_depression =
"nautical"
226 self.
_check_event_check_event(utc_point_in_time,
"dusk", PHASE_NAUTICAL_TWILIGHT)
227 self.
locationlocation.solar_depression =
"astronomical"
228 self.
_check_event_check_event(utc_point_in_time,
"dusk", PHASE_ASTRONOMICAL_TWILIGHT)
230 self.
locationlocation.solar_depression =
"civil"
236 if self.
phasephase
is None:
239 self.
phasephase = PHASE_DAY
241 self.
phasephase = PHASE_SMALL_DAY
242 elif elevation >= -6:
243 self.
phasephase = PHASE_TWILIGHT
244 elif elevation >= -12:
245 self.
phasephase = PHASE_NAUTICAL_TWILIGHT
246 elif elevation >= -18:
247 self.
phasephase = PHASE_ASTRONOMICAL_TWILIGHT
249 self.
phasephase = PHASE_NIGHT
254 "sun phase_update@%s: phase=%s", utc_point_in_time.isoformat(), self.
phasephase
265 _LOGGER.debug(
"next time: %s", self.
_next_change_next_change.isoformat())
269 """Calculate the position of the sun."""
271 utc_point_in_time = dt_util.utcnow()
280 "sun position_update@%s: elevation=%s azimuth=%s",
281 utc_point_in_time.isoformat(),
290 assert self.
phasephase
291 delta = _PHASE_UPDATES[self.
phasephase]
294 if utc_point_in_time + delta * 1.25 > self.
_next_change_next_change:
datetime _check_event(self, datetime utc_point_in_time, str sun_event, str|None before)
None remove_listeners(self)
dict[str, Any] extra_state_attributes(self)
None update_location(self, Event|None _=None, bool initial=False)
None __init__(self, HomeAssistant hass)
_update_sun_position_listener
None update_events(self, datetime|None now=None)
None update_sun_position(self, datetime|None now=None)
None async_write_ha_state(self)
None async_dispatcher_send(HomeAssistant hass, str signal, *Any args)
tuple[astral.location.Location, astral.Elevation] get_astral_location(HomeAssistant hass)
datetime.datetime get_location_astral_event_next(astral.location.Location location, astral.Elevation elevation, str event, datetime.datetime|None utc_point_in_time=None, datetime.timedelta|None offset=None)