Ok, below is the code. I didn’t want to check this into github because it’s kind of a special use case. (I’m considering adding config parameters to enable the desired set of attributes.) Anyway, it now has the additional attributes, and they are formatted as strings in the form of HH:MM:SS.
Here is the code for custom_components/sun.py
:
"""
Support for functionality to keep track of the sun.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/sun/
"""
import asyncio
import logging
from datetime import timedelta
from homeassistant.const import CONF_ELEVATION
from homeassistant.core import callback
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.event import (
async_track_point_in_utc_time, async_track_utc_time_change)
from homeassistant.helpers.sun import (
get_astral_location, get_astral_event_next, get_astral_event_date)
from homeassistant.util import dt as dt_util
_LOGGER = logging.getLogger(__name__)
DOMAIN = 'sun'
ENTITY_ID = 'sun.sun'
STATE_ABOVE_HORIZON = 'above_horizon'
STATE_BELOW_HORIZON = 'below_horizon'
STATE_ATTR_AZIMUTH = 'azimuth'
STATE_ATTR_ELEVATION = 'elevation'
STATE_ATTR_NEXT_DAWN = 'next_dawn'
STATE_ATTR_NEXT_DUSK = 'next_dusk'
STATE_ATTR_NEXT_MIDNIGHT = 'next_midnight'
STATE_ATTR_NEXT_NOON = 'next_noon'
STATE_ATTR_NEXT_RISING = 'next_rising'
STATE_ATTR_NEXT_SETTING = 'next_setting'
STATE_ATTR_SUNRISE = 'sunrise'
STATE_ATTR_SUNSET = 'sunset'
STATE_ATTR_DAYLIGHT = 'daylight'
STATE_ATTR_PREV_DAYLIGHT = 'prev_daylight'
STATE_ATTR_NEXT_DAYLIGHT = 'next_daylight'
@asyncio.coroutine
def async_setup(hass, config):
"""Track the state of the sun."""
if config.get(CONF_ELEVATION) is not None:
_LOGGER.warning(
"Elevation is now configured in home assistant core. "
"See https://home-assistant.io/docs/configuration/basic/")
sun = Sun(hass, get_astral_location(hass))
sun.point_in_time_listener(dt_util.utcnow())
return True
class Sun(Entity):
"""Representation of the Sun."""
entity_id = ENTITY_ID
def __init__(self, hass, location):
"""Initialize the sun."""
self.hass = hass
self.location = location
self._state = self.next_rising = self.next_setting = None
self.sunrise = self.sunset = None
self.daylight = self.prev_daylight = self.next_daylight = None
self.next_dawn = self.next_dusk = None
self.next_midnight = self.next_noon = None
self.solar_elevation = self.solar_azimuth = None
async_track_utc_time_change(hass, self.timer_update, second=30)
@property
def name(self):
"""Return the name."""
return "Sun"
@property
def state(self):
"""Return the state of the sun."""
if self.next_rising > self.next_setting:
return STATE_ABOVE_HORIZON
return STATE_BELOW_HORIZON
@property
def state_attributes(self):
"""Return the state attributes of the sun."""
return {
STATE_ATTR_NEXT_DAWN: self.next_dawn.isoformat(),
STATE_ATTR_NEXT_DUSK: self.next_dusk.isoformat(),
STATE_ATTR_NEXT_MIDNIGHT: self.next_midnight.isoformat(),
STATE_ATTR_NEXT_NOON: self.next_noon.isoformat(),
STATE_ATTR_NEXT_RISING: self.next_rising.isoformat(),
STATE_ATTR_NEXT_SETTING: self.next_setting.isoformat(),
STATE_ATTR_SUNRISE: self.sunrise.isoformat(),
STATE_ATTR_SUNSET: self.sunset.isoformat(),
STATE_ATTR_DAYLIGHT: str(self.daylight),
STATE_ATTR_PREV_DAYLIGHT: str(self.prev_daylight),
STATE_ATTR_NEXT_DAYLIGHT: str(self.next_daylight),
STATE_ATTR_ELEVATION: round(self.solar_elevation, 2),
STATE_ATTR_AZIMUTH: round(self.solar_azimuth, 2)
}
@property
def next_change(self):
"""Datetime when the next change to the state is."""
# next_midnight is next solar midnight. So get actual midnight,
# but subtract a second because point_in_time_listener() will add one.
midnight = dt_util.as_utc(dt_util.start_of_local_day(
dt_util.now()+timedelta(1))-timedelta(seconds=1))
return min(self.next_dawn, self.next_dusk, self.next_midnight,
self.next_noon, self.next_rising, self.next_setting, midnight)
@callback
def update_as_of(self, utc_point_in_time):
"""Update the attributes containing solar events."""
self.next_dawn = get_astral_event_next(
self.hass, 'dawn', utc_point_in_time)
self.next_dusk = get_astral_event_next(
self.hass, 'dusk', utc_point_in_time)
self.next_midnight = get_astral_event_next(
self.hass, 'solar_midnight', utc_point_in_time)
self.next_noon = get_astral_event_next(
self.hass, 'solar_noon', utc_point_in_time)
self.next_rising = get_astral_event_next(
self.hass, 'sunrise', utc_point_in_time)
self.next_setting = get_astral_event_next(
self.hass, 'sunset', utc_point_in_time)
self.sunrise = get_astral_event_date(
self.hass, 'sunrise', utc_point_in_time)
self.sunset = get_astral_event_date(
self.hass, 'sunset', utc_point_in_time)
d = get_astral_event_date(
self.hass, 'daylight', utc_point_in_time)
self.daylight = d[1] - d[0]
d = get_astral_event_date(
self.hass, 'daylight', utc_point_in_time-timedelta(days=1))
self.prev_daylight = d[1] - d[0]
d = get_astral_event_date(
self.hass, 'daylight', utc_point_in_time+timedelta(days=1))
self.next_daylight = d[1] - d[0]
@callback
def update_sun_position(self, utc_point_in_time):
"""Calculate the position of the sun."""
self.solar_azimuth = self.location.solar_azimuth(utc_point_in_time)
self.solar_elevation = self.location.solar_elevation(utc_point_in_time)
@callback
def point_in_time_listener(self, now):
"""Run when the state of the sun has changed."""
self.update_sun_position(now)
self.update_as_of(now)
self.async_schedule_update_ha_state()
# Schedule next update at next_change+1 second so sun state has changed
async_track_point_in_utc_time(
self.hass, self.point_in_time_listener,
self.next_change + timedelta(seconds=1))
@callback
def timer_update(self, time):
"""Needed to update solar elevation and azimuth."""
self.update_sun_position(time)
self.async_schedule_update_ha_state()