From 3650a2fa855171fa5a75a0fd4e0b05dd4b2a5219 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sat, 25 Apr 2015 17:43:22 -0700 Subject: [PATCH] Sun component now uses Entity ABC --- homeassistant/components/sun.py | 147 ++++++++++++++++++-------------- 1 file changed, 84 insertions(+), 63 deletions(-) diff --git a/homeassistant/components/sun.py b/homeassistant/components/sun.py index 52baf430579..a7228a1e42f 100644 --- a/homeassistant/components/sun.py +++ b/homeassistant/components/sun.py @@ -24,8 +24,14 @@ which event (sunset or sunrise) and the offset. import logging from datetime import datetime, timedelta -from homeassistant.util import str_to_datetime, datetime_to_str +try: + import ephem +except ImportError: + # Error will be raised during setup + ephem = None +from homeassistant.util import str_to_datetime, datetime_to_str +from homeassistant.helpers.entity import Entity from homeassistant.components.scheduler import ServiceEventListener DEPENDENCIES = [] @@ -80,78 +86,93 @@ def setup(hass, config): """ Tracks the state of the sun. """ logger = logging.getLogger(__name__) - try: - import ephem - except ImportError: + if ephem is None: logger.exception("Error while importing dependency ephem.") return False - sun = ephem.Sun() # pylint: disable=no-member - - latitude = str(hass.config.latitude) - longitude = str(hass.config.longitude) - - # Validate latitude and longitude - observer = ephem.Observer() - - errors = [] - - try: - observer.lat = latitude # pylint: disable=assigning-non-slot - except ValueError: - errors.append("invalid value for latitude given: {}".format(latitude)) - - try: - observer.long = longitude # pylint: disable=assigning-non-slot - except ValueError: - errors.append("invalid value for latitude given: {}".format(latitude)) - - if errors: - logger.error("Error setting up: %s", ", ".join(errors)) + if None in (hass.config.latitude, hass.config.longitude): + logger.error("Latitude or longitude not set in Home Assistant config") return False - def update_sun_state(now): - """ Method to update the current state of the sun and - set time of next setting and rising. """ - utc_offset = datetime.utcnow() - datetime.now() - utc_now = now + utc_offset + sun = Sun(hass, str(hass.config.latitude), str(hass.config.longitude)) - observer = ephem.Observer() - observer.lat = latitude # pylint: disable=assigning-non-slot - observer.long = longitude # pylint: disable=assigning-non-slot - - next_rising_dt = ephem.localtime( - observer.next_rising(sun, start=utc_now)) - next_setting_dt = ephem.localtime( - observer.next_setting(sun, start=utc_now)) - - if next_rising_dt > next_setting_dt: - new_state = STATE_ABOVE_HORIZON - next_change = next_setting_dt - - else: - new_state = STATE_BELOW_HORIZON - next_change = next_rising_dt - - logger.info("%s. Next change: %s", - new_state, next_change.strftime("%H:%M")) - - state_attributes = { - STATE_ATTR_NEXT_RISING: datetime_to_str(next_rising_dt), - STATE_ATTR_NEXT_SETTING: datetime_to_str(next_setting_dt) - } - - hass.states.set(ENTITY_ID, new_state, state_attributes) - - # +1 second so Ephem will report it has set - hass.track_point_in_time(update_sun_state, - next_change + timedelta(seconds=1)) - - update_sun_state(datetime.now()) + try: + sun.point_in_time_listener(datetime.now()) + except ValueError: + # Raised when invalid latitude or longitude is given to Observer + logger.exception("Invalid value for latitude or longitude") + return False return True +class Sun(Entity): + """ Represents the Sun. """ + + entity_id = ENTITY_ID + + def __init__(self, hass, latitude, longitude): + self.hass = hass + self.latitude = latitude + self.longitude = longitude + + self._state = self.next_rising = self.next_setting = None + + @property + def should_poll(self): + """ We trigger updates ourselves after sunset/sunrise """ + return False + + @property + def name(self): + return "Sun" + + @property + def state(self): + if self.next_rising > self.next_setting: + return STATE_ABOVE_HORIZON + + return STATE_BELOW_HORIZON + + @property + def state_attributes(self): + return { + STATE_ATTR_NEXT_RISING: datetime_to_str(self.next_rising), + STATE_ATTR_NEXT_SETTING: datetime_to_str(self.next_setting) + } + + @property + def next_change(self): + """ Returns the datetime when the next change to the state is. """ + return min(self.next_rising, self.next_setting) + + def update_as_of(self, point_in_time): + """ Calculate sun state at a point in time. """ + utc_offset = datetime.utcnow() - datetime.now() + utc_now = point_in_time + utc_offset + + sun = ephem.Sun() # pylint: disable=no-member + + # Setting invalid latitude and longitude to observer raises ValueError + observer = ephem.Observer() + observer.lat = self.latitude # pylint: disable=assigning-non-slot + observer.long = self.longitude # pylint: disable=assigning-non-slot + + self.next_rising = ephem.localtime( + observer.next_rising(sun, start=utc_now)) + self.next_setting = ephem.localtime( + observer.next_setting(sun, start=utc_now)) + + def point_in_time_listener(self, now): + """ Called when the state of the sun has changed. """ + self.update_as_of(now) + self.update_ha_state() + + # Schedule next update at next_change+1 second so sun state has changed + self.hass.track_point_in_time(self.point_in_time_listener, + self.next_change + timedelta(seconds=1)) + + def create_event_listener(schedule, event_listener_data): """ Create a sun event listener based on the description. """