diff --git a/homeassistant/components/automation/sun.py b/homeassistant/components/automation/sun.py index 3ce84d60a91..dfed411745f 100644 --- a/homeassistant/components/automation/sun.py +++ b/homeassistant/components/automation/sun.py @@ -16,8 +16,6 @@ from homeassistant.const import ( from homeassistant.helpers.event import async_track_sunrise, async_track_sunset import homeassistant.helpers.config_validation as cv -DEPENDENCIES = ['sun'] - _LOGGER = logging.getLogger(__name__) TRIGGER_SCHEMA = vol.Schema({ diff --git a/homeassistant/components/device_sun_light_trigger.py b/homeassistant/components/device_sun_light_trigger.py index 9119394e357..a1297c5c118 100644 --- a/homeassistant/components/device_sun_light_trigger.py +++ b/homeassistant/components/device_sun_light_trigger.py @@ -14,12 +14,13 @@ from homeassistant.core import callback import homeassistant.util.dt as dt_util from homeassistant.const import STATE_HOME, STATE_NOT_HOME from homeassistant.helpers.event import ( - async_track_point_in_time, async_track_state_change) + async_track_point_in_utc_time, async_track_state_change) +from homeassistant.helpers.sun import is_up, get_astral_event_next from homeassistant.loader import get_component import homeassistant.helpers.config_validation as cv DOMAIN = 'device_sun_light_trigger' -DEPENDENCIES = ['light', 'device_tracker', 'group', 'sun'] +DEPENDENCIES = ['light', 'device_tracker', 'group'] CONF_DEVICE_GROUP = 'device_group' CONF_DISABLE_TURN_OFF = 'disable_turn_off' @@ -50,7 +51,6 @@ def async_setup(hass, config): device_tracker = get_component('device_tracker') group = get_component('group') light = get_component('light') - sun = get_component('sun') conf = config[DOMAIN] disable_turn_off = conf.get(CONF_DISABLE_TURN_OFF) light_group = conf.get(CONF_LIGHT_GROUP, light.ENTITY_ID_ALL_LIGHTS) @@ -78,7 +78,7 @@ def async_setup(hass, config): Async friendly. """ - next_setting = sun.next_setting(hass) + next_setting = get_astral_event_next(hass, 'sunset') if not next_setting: return None return next_setting - LIGHT_TRANSITION_TIME * len(light_ids) @@ -103,7 +103,7 @@ def async_setup(hass, config): # Track every time sun rises so we can schedule a time-based # pre-sun set event @callback - def schedule_light_turn_on(entity, old_state, new_state): + def schedule_light_turn_on(now): """Turn on all the lights at the moment sun sets. We will schedule to have each light start after one another @@ -114,26 +114,26 @@ def async_setup(hass, config): return for index, light_id in enumerate(light_ids): - async_track_point_in_time( + async_track_point_in_utc_time( hass, async_turn_on_factory(light_id), start_point + index * LIGHT_TRANSITION_TIME) - async_track_state_change(hass, sun.ENTITY_ID, schedule_light_turn_on, - sun.STATE_BELOW_HORIZON, sun.STATE_ABOVE_HORIZON) + async_track_point_in_utc_time(hass, schedule_light_turn_on, + get_astral_event_next(hass, 'sunrise')) # If the sun is already above horizon schedule the time-based pre-sun set # event. - if sun.is_on(hass): - schedule_light_turn_on(None, None, None) + if is_up(hass): + schedule_light_turn_on(None) @callback def check_light_on_dev_state_change(entity, old_state, new_state): """Handle tracked device state changes.""" lights_are_on = group.is_on(hass, light_group) - light_needed = not (lights_are_on or sun.is_on(hass)) + light_needed = not (lights_are_on or is_up(hass)) # These variables are needed for the elif check - now = dt_util.now() + now = dt_util.utcnow() start_point = calc_time_for_light_when_sunset() # Do we need lights? @@ -146,7 +146,7 @@ def async_setup(hass, config): # Check this by seeing if current time is later then the point # in time when we would start putting the lights on. elif (start_point and - start_point < now < sun.next_setting(hass)): + start_point < now < get_astral_event_next(hass, 'sunset')): # Check for every light if it would be on if someone was home # when the fading in started and turn it on if so diff --git a/homeassistant/components/sensor/moon.py b/homeassistant/components/sensor/moon.py index dc890c0f3cd..ca79e5241c4 100644 --- a/homeassistant/components/sensor/moon.py +++ b/homeassistant/components/sensor/moon.py @@ -15,8 +15,6 @@ import homeassistant.util.dt as dt_util from homeassistant.helpers.entity import Entity import homeassistant.helpers.config_validation as cv -REQUIREMENTS = ['astral==1.4'] - _LOGGER = logging.getLogger(__name__) DEFAULT_NAME = 'Moon' diff --git a/homeassistant/components/sun.py b/homeassistant/components/sun.py index b2af30d8438..8254b4b2f0e 100644 --- a/homeassistant/components/sun.py +++ b/homeassistant/components/sun.py @@ -4,20 +4,18 @@ 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 -import voluptuous as vol - from homeassistant.const import CONF_ELEVATION +from homeassistant.core import callback from homeassistant.helpers.entity import Entity from homeassistant.helpers.event import ( - track_point_in_utc_time, track_utc_time_change) + async_track_point_in_utc_time, async_track_utc_time_change) +from homeassistant.helpers.sun import ( + get_astral_location, get_astral_event_next) from homeassistant.util import dt as dt_util -import homeassistant.helpers.config_validation as cv -import homeassistant.util as util - -REQUIREMENTS = ['astral==1.4'] _LOGGER = logging.getLogger(__name__) @@ -37,223 +35,16 @@ STATE_ATTR_NEXT_NOON = 'next_noon' STATE_ATTR_NEXT_RISING = 'next_rising' STATE_ATTR_NEXT_SETTING = 'next_setting' -CONFIG_SCHEMA = vol.Schema({ - DOMAIN: vol.Schema({ - vol.Optional(CONF_ELEVATION): cv.positive_int, - }), -}, extra=vol.ALLOW_EXTRA) - -def is_on(hass, entity_id=None): - """Test if the sun is currently up based on the statemachine.""" - entity_id = entity_id or ENTITY_ID - - return hass.states.is_state(entity_id, STATE_ABOVE_HORIZON) - - -def next_dawn(hass, entity_id=None): - """Local datetime object of the next dawn. - - Async friendly. - """ - utc_next = next_dawn_utc(hass, entity_id) - - return dt_util.as_local(utc_next) if utc_next else None - - -def next_dawn_utc(hass, entity_id=None): - """UTC datetime object of the next dawn. - - Async friendly. - """ - entity_id = entity_id or ENTITY_ID - - state = hass.states.get(ENTITY_ID) - - try: - return dt_util.parse_datetime( - state.attributes[STATE_ATTR_NEXT_DAWN]) - except (AttributeError, KeyError): - # AttributeError if state is None - # KeyError if STATE_ATTR_NEXT_DAWN does not exist - return None - - -def next_dusk(hass, entity_id=None): - """Local datetime object of the next dusk. - - Async friendly. - """ - utc_next = next_dusk_utc(hass, entity_id) - - return dt_util.as_local(utc_next) if utc_next else None - - -def next_dusk_utc(hass, entity_id=None): - """UTC datetime object of the next dusk. - - Async friendly. - """ - entity_id = entity_id or ENTITY_ID - - state = hass.states.get(ENTITY_ID) - - try: - return dt_util.parse_datetime( - state.attributes[STATE_ATTR_NEXT_DUSK]) - except (AttributeError, KeyError): - # AttributeError if state is None - # KeyError if STATE_ATTR_NEXT_DUSK does not exist - return None - - -def next_midnight(hass, entity_id=None): - """Local datetime object of the next midnight. - - Async friendly. - """ - utc_next = next_midnight_utc(hass, entity_id) - - return dt_util.as_local(utc_next) if utc_next else None - - -def next_midnight_utc(hass, entity_id=None): - """UTC datetime object of the next midnight. - - Async friendly. - """ - entity_id = entity_id or ENTITY_ID - - state = hass.states.get(ENTITY_ID) - - try: - return dt_util.parse_datetime( - state.attributes[STATE_ATTR_NEXT_MIDNIGHT]) - except (AttributeError, KeyError): - # AttributeError if state is None - # KeyError if STATE_ATTR_NEXT_MIDNIGHT does not exist - return None - - -def next_noon(hass, entity_id=None): - """Local datetime object of the next solar noon. - - Async friendly. - """ - utc_next = next_noon_utc(hass, entity_id) - - return dt_util.as_local(utc_next) if utc_next else None - - -def next_noon_utc(hass, entity_id=None): - """UTC datetime object of the next noon. - - Async friendly. - """ - entity_id = entity_id or ENTITY_ID - - state = hass.states.get(ENTITY_ID) - - try: - return dt_util.parse_datetime( - state.attributes[STATE_ATTR_NEXT_NOON]) - except (AttributeError, KeyError): - # AttributeError if state is None - # KeyError if STATE_ATTR_NEXT_NOON does not exist - return None - - -def next_setting(hass, entity_id=None): - """Local datetime object of the next sun setting. - - Async friendly. - """ - utc_next = next_setting_utc(hass, entity_id) - - return dt_util.as_local(utc_next) if utc_next else None - - -def next_setting_utc(hass, entity_id=None): - """UTC datetime object of the next sun setting. - - Async friendly. - """ - entity_id = entity_id or ENTITY_ID - - state = hass.states.get(ENTITY_ID) - - try: - return dt_util.parse_datetime( - state.attributes[STATE_ATTR_NEXT_SETTING]) - except (AttributeError, KeyError): - # AttributeError if state is None - # KeyError if STATE_ATTR_NEXT_SETTING does not exist - return None - - -def next_rising(hass, entity_id=None): - """Local datetime object of the next sun rising. - - Async friendly. - """ - utc_next = next_rising_utc(hass, entity_id) - - return dt_util.as_local(utc_next) if utc_next else None - - -def next_rising_utc(hass, entity_id=None): - """UTC datetime object of the next sun rising. - - Async friendly. - """ - entity_id = entity_id or ENTITY_ID - - state = hass.states.get(ENTITY_ID) - - try: - return dt_util.parse_datetime(state.attributes[STATE_ATTR_NEXT_RISING]) - except (AttributeError, KeyError): - # AttributeError if state is None - # KeyError if STATE_ATTR_NEXT_RISING does not exist - return None - - -def setup(hass, config): +@asyncio.coroutine +def async_setup(hass, config): """Track the state of the sun.""" - if None in (hass.config.latitude, hass.config.longitude): - _LOGGER.error("Latitude or longitude not set in Home Assistant config") - return False + 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/") - latitude = util.convert(hass.config.latitude, float) - longitude = util.convert(hass.config.longitude, float) - errors = [] - - if latitude is None: - errors.append('Latitude needs to be a decimal value') - elif -90 > latitude < 90: - errors.append('Latitude needs to be -90 .. 90') - - if longitude is None: - errors.append('Longitude needs to be a decimal value') - elif -180 > longitude < 180: - errors.append('Longitude needs to be -180 .. 180') - - if errors: - _LOGGER.error('Invalid configuration received: %s', ", ".join(errors)) - return False - - platform_config = config.get(DOMAIN, {}) - - elevation = platform_config.get(CONF_ELEVATION) - if elevation is None: - elevation = hass.config.elevation or 0 - - from astral import Location - - location = Location(('', '', latitude, longitude, - hass.config.time_zone.zone, elevation)) - - sun = Sun(hass, location) + sun = Sun(hass, get_astral_location(hass)) sun.point_in_time_listener(dt_util.utcnow()) return True @@ -273,7 +64,7 @@ class Sun(Entity): self.next_midnight = self.next_noon = None self.solar_elevation = self.solar_azimuth = 0 - track_utc_time_change(hass, self.timer_update, second=30) + async_track_utc_time_change(hass, self.timer_update, second=30) @property def name(self): @@ -308,64 +99,41 @@ class Sun(Entity): return min(self.next_dawn, self.next_dusk, self.next_midnight, self.next_noon, self.next_rising, self.next_setting) - @staticmethod - def get_next_solar_event(callable_on_astral_location, - utc_point_in_time, mod, increment): - """Calculate sun state at a point in UTC time.""" - import astral - - while True: - try: - next_dt = callable_on_astral_location( - utc_point_in_time + timedelta(days=mod), local=False) - if next_dt > utc_point_in_time: - break - except astral.AstralError: - pass - mod += increment - - return next_dt - + @callback def update_as_of(self, utc_point_in_time): """Update the attributes containing solar events.""" - self.next_dawn = Sun.get_next_solar_event( - self.location.dawn, utc_point_in_time, -1, 1) - self.next_dusk = Sun.get_next_solar_event( - self.location.dusk, utc_point_in_time, -1, 1) - self.next_midnight = Sun.get_next_solar_event( - self.location.solar_midnight, utc_point_in_time, -1, 1) - self.next_noon = Sun.get_next_solar_event( - self.location.solar_noon, utc_point_in_time, -1, 1) - self.next_rising = Sun.get_next_solar_event( - self.location.sunrise, utc_point_in_time, -1, 1) - self.next_setting = Sun.get_next_solar_event( - self.location.sunset, utc_point_in_time, -1, 1) + 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) + @callback def update_sun_position(self, utc_point_in_time): """Calculate the position of the sun.""" - from astral import Astral - - self.solar_azimuth = Astral().solar_azimuth( - utc_point_in_time, - self.location.latitude, - self.location.longitude) - - self.solar_elevation = Astral().solar_elevation( - utc_point_in_time, - self.location.latitude, - self.location.longitude) + 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_as_of(now) - self.schedule_update_ha_state() + self.hass.async_add_job(self.async_update_ha_state()) # Schedule next update at next_change+1 second so sun state has changed - track_point_in_utc_time( + 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.schedule_update_ha_state() + self.hass.async_add_job(self.async_update_ha_state()) diff --git a/homeassistant/components/switch/flux.py b/homeassistant/components/switch/flux.py index 2052ffc4c15..daa4d1f8cd1 100644 --- a/homeassistant/components/switch/flux.py +++ b/homeassistant/components/switch/flux.py @@ -11,18 +11,18 @@ import logging import voluptuous as vol from homeassistant.components.light import is_on, turn_on -from homeassistant.components.sun import next_setting, next_rising from homeassistant.components.switch import DOMAIN, SwitchDevice from homeassistant.const import CONF_NAME, CONF_PLATFORM from homeassistant.helpers.event import track_time_change +from homeassistant.helpers.sun import get_astral_event_date from homeassistant.util.color import ( color_temperature_to_rgb, color_RGB_to_xy, color_temperature_kelvin_to_mired) from homeassistant.util.dt import now as dt_now import homeassistant.helpers.config_validation as cv -DEPENDENCIES = ['sun', 'light'] -SUN = "sun.sun" +DEPENDENCIES = ['light'] + _LOGGER = logging.getLogger(__name__) CONF_LIGHTS = 'lights' @@ -159,8 +159,7 @@ class FluxSwitch(SwitchDevice): """Update all the lights using flux.""" if now is None: now = dt_now() - sunset = next_setting(self.hass, SUN).replace( - day=now.day, month=now.month, year=now.year) + sunset = get_astral_event_date(self.hass, 'sunset', now.date()) start_time = self.find_start_time(now) stop_time = now.replace( hour=self._stop_time.hour, minute=self._stop_time.minute, @@ -221,6 +220,5 @@ class FluxSwitch(SwitchDevice): hour=self._start_time.hour, minute=self._start_time.minute, second=0) else: - sunrise = next_rising(self.hass, SUN).replace( - day=now.day, month=now.month, year=now.year) + sunrise = get_astral_event_date(self.hass, 'sunrise', now.date()) return sunrise diff --git a/homeassistant/helpers/condition.py b/homeassistant/helpers/condition.py index bbfb19f7806..a0753b0f766 100644 --- a/homeassistant/helpers/condition.py +++ b/homeassistant/helpers/condition.py @@ -7,8 +7,7 @@ import sys from homeassistant.helpers.typing import ConfigType from homeassistant.core import HomeAssistant -from homeassistant.components import ( - zone as zone_cmp, sun as sun_cmp) +from homeassistant.components import zone as zone_cmp from homeassistant.const import ( ATTR_GPS_ACCURACY, ATTR_LATITUDE, ATTR_LONGITUDE, CONF_ENTITY_ID, CONF_VALUE_TEMPLATE, CONF_CONDITION, @@ -17,6 +16,7 @@ from homeassistant.const import ( CONF_BELOW, CONF_ABOVE) from homeassistant.exceptions import TemplateError, HomeAssistantError import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.sun import get_astral_event_date import homeassistant.util.dt as dt_util from homeassistant.util.async import run_callback_threadsafe @@ -234,24 +234,34 @@ def state_from_config(config, config_validation=True): def sun(hass, before=None, after=None, before_offset=None, after_offset=None): """Test if current time matches sun requirements.""" - now = dt_util.now().time() + utcnow = dt_util.utcnow() + today = dt_util.as_local(utcnow).date() before_offset = before_offset or timedelta(0) after_offset = after_offset or timedelta(0) - if before == SUN_EVENT_SUNRISE and now > (sun_cmp.next_rising(hass) + - before_offset).time(): + sunrise = get_astral_event_date(hass, 'sunrise', today) + sunset = get_astral_event_date(hass, 'sunset', today) + + if sunrise is None and (before == SUN_EVENT_SUNRISE or + after == SUN_EVENT_SUNRISE): + # There is no sunrise today return False - elif before == SUN_EVENT_SUNSET and now > (sun_cmp.next_setting(hass) + - before_offset).time(): + if sunset is None and (before == SUN_EVENT_SUNSET or + after == SUN_EVENT_SUNSET): + # There is no sunset today return False - if after == SUN_EVENT_SUNRISE and now < (sun_cmp.next_rising(hass) + - after_offset).time(): + if before == SUN_EVENT_SUNRISE and utcnow > sunrise + before_offset: return False - elif after == SUN_EVENT_SUNSET and now < (sun_cmp.next_setting(hass) + - after_offset).time(): + elif before == SUN_EVENT_SUNSET and utcnow > sunset + before_offset: + return False + + if after == SUN_EVENT_SUNRISE and utcnow < sunrise + after_offset: + return False + + elif after == SUN_EVENT_SUNSET and utcnow < sunset + after_offset: return False return True diff --git a/homeassistant/helpers/event.py b/homeassistant/helpers/event.py index 0cdcca42eca..d3ad93d3646 100644 --- a/homeassistant/helpers/event.py +++ b/homeassistant/helpers/event.py @@ -1,7 +1,7 @@ """Helpers for listening to events.""" import functools as ft -from datetime import timedelta +from homeassistant.helpers.sun import get_astral_event_next from ..core import HomeAssistant, callback from ..const import ( ATTR_NOW, EVENT_STATE_CHANGED, EVENT_TIME_CHANGED, MATCH_ALL) @@ -197,29 +197,20 @@ track_time_interval = threaded_listener_factory(async_track_time_interval) @callback def async_track_sunrise(hass, action, offset=None): """Add a listener that will fire a specified offset from sunrise daily.""" - from homeassistant.components import sun - offset = offset or timedelta() remove = None - def next_rise(): - """Return the next sunrise.""" - next_time = sun.next_rising_utc(hass) + offset - - while next_time < dt_util.utcnow(): - next_time = next_time + timedelta(days=1) - - return next_time - @callback def sunrise_automation_listener(now): """Handle points in time to execute actions.""" nonlocal remove remove = async_track_point_in_utc_time( - hass, sunrise_automation_listener, next_rise()) + hass, sunrise_automation_listener, get_astral_event_next( + hass, 'sunrise', offset=offset)) hass.async_run_job(action) remove = async_track_point_in_utc_time( - hass, sunrise_automation_listener, next_rise()) + hass, sunrise_automation_listener, get_astral_event_next( + hass, 'sunrise', offset=offset)) def remove_listener(): """Remove sunset listener.""" @@ -234,29 +225,20 @@ track_sunrise = threaded_listener_factory(async_track_sunrise) @callback def async_track_sunset(hass, action, offset=None): """Add a listener that will fire a specified offset from sunset daily.""" - from homeassistant.components import sun - offset = offset or timedelta() remove = None - def next_set(): - """Return next sunrise.""" - next_time = sun.next_setting_utc(hass) + offset - - while next_time < dt_util.utcnow(): - next_time = next_time + timedelta(days=1) - - return next_time - @callback def sunset_automation_listener(now): """Handle points in time to execute actions.""" nonlocal remove remove = async_track_point_in_utc_time( - hass, sunset_automation_listener, next_set()) + hass, sunset_automation_listener, get_astral_event_next( + hass, 'sunset', offset=offset)) hass.async_run_job(action) remove = async_track_point_in_utc_time( - hass, sunset_automation_listener, next_set()) + hass, sunset_automation_listener, get_astral_event_next( + hass, 'sunset', offset=offset)) def remove_listener(): """Remove sunset listener.""" diff --git a/homeassistant/helpers/sun.py b/homeassistant/helpers/sun.py new file mode 100644 index 00000000000..157225c9903 --- /dev/null +++ b/homeassistant/helpers/sun.py @@ -0,0 +1,87 @@ +"""Helpers for sun events.""" +import datetime + +from homeassistant.core import callback +from homeassistant.util import dt as dt_util + +DATA_LOCATION_CACHE = 'astral_location_cache' + + +@callback +def get_astral_location(hass): + """Get an astral location for the current hass configuration.""" + from astral import Location + + latitude = hass.config.latitude + longitude = hass.config.longitude + timezone = hass.config.time_zone.zone + elevation = hass.config.elevation + info = ('', '', latitude, longitude, timezone, elevation) + + # Cache astral locations so they aren't recreated with the same args + if DATA_LOCATION_CACHE not in hass.data: + hass.data[DATA_LOCATION_CACHE] = {} + + if info not in hass.data[DATA_LOCATION_CACHE]: + hass.data[DATA_LOCATION_CACHE][info] = Location(info) + + return hass.data[DATA_LOCATION_CACHE][info] + + +@callback +def get_astral_event_next(hass, event, utc_point_in_time=None, offset=None): + """Calculate the next specified solar event.""" + import astral + + location = get_astral_location(hass) + + if offset is None: + offset = datetime.timedelta() + + if utc_point_in_time is None: + utc_point_in_time = dt_util.utcnow() + + mod = -1 + while True: + try: + next_dt = getattr(location, event)( + dt_util.as_local(utc_point_in_time).date() + + datetime.timedelta(days=mod), + local=False) + offset + if next_dt > utc_point_in_time: + return next_dt + except astral.AstralError: + pass + mod += 1 + + +@callback +def get_astral_event_date(hass, event, date=None): + """Calculate the astral event time for the specified date.""" + import astral + + location = get_astral_location(hass) + + if date is None: + date = dt_util.now().date() + + if isinstance(date, datetime.datetime): + date = dt_util.as_local(date).date() + + try: + return getattr(location, event)(date, local=False) + except astral.AstralError: + # Event never occurs for specified date. + return None + + +@callback +def is_up(hass, utc_point_in_time=None): + """Calculate if the sun is currently up.""" + if utc_point_in_time is None: + utc_point_in_time = dt_util.utcnow() + + next_sunrise = get_astral_event_next(hass, 'sunrise', utc_point_in_time) + next_sunset = get_astral_event_next(hass, 'sunset', utc_point_in_time) + + return next_sunrise > next_sunset diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index f6774a0d5dc..39314f963ae 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -8,3 +8,4 @@ typing>=3,<4 aiohttp==2.0.7 async_timeout==1.2.1 chardet==3.0.2 +astral==1.4 diff --git a/requirements_all.txt b/requirements_all.txt index d1767167ad9..9aa450753cc 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -9,6 +9,7 @@ typing>=3,<4 aiohttp==2.0.7 async_timeout==1.2.1 chardet==3.0.2 +astral==1.4 # homeassistant.components.nuimo_controller --only-binary=all https://github.com/getSenic/nuimo-linux-python/archive/29fc42987f74d8090d0e2382e8f248ff5990b8c9.zip#nuimo==1.0.0 @@ -66,10 +67,6 @@ apcaccess==0.0.4 # homeassistant.components.notify.apns apns2==0.1.1 -# homeassistant.components.sun -# homeassistant.components.sensor.moon -astral==1.4 - # homeassistant.components.light.avion # avion==0.6 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 72c264fd775..0253f41f734 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -37,10 +37,6 @@ aiohttp_cors==0.5.3 # homeassistant.components.notify.apns apns2==0.1.1 -# homeassistant.components.sun -# homeassistant.components.sensor.moon -astral==1.4 - # homeassistant.components.datadog datadog==0.15.0 diff --git a/setup.py b/setup.py index d0f4ccbd75b..2cdcad544fb 100755 --- a/setup.py +++ b/setup.py @@ -24,7 +24,8 @@ REQUIRES = [ 'typing>=3,<4', 'aiohttp==2.0.7', 'async_timeout==1.2.1', - 'chardet==3.0.2' + 'chardet==3.0.2', + 'astral==1.4', ] setup( diff --git a/tests/common.py b/tests/common.py index 1585cb33e23..9d8f2e33065 100644 --- a/tests/common.py +++ b/tests/common.py @@ -3,7 +3,6 @@ import asyncio import functools as ft import os import sys -from datetime import timedelta from unittest.mock import patch, MagicMock, Mock from io import StringIO import logging @@ -25,7 +24,7 @@ from homeassistant.const import ( STATE_ON, STATE_OFF, DEVICE_DEFAULT_NAME, EVENT_TIME_CHANGED, EVENT_STATE_CHANGED, EVENT_PLATFORM_DISCOVERED, ATTR_SERVICE, ATTR_DISCOVERED, SERVER_PORT, EVENT_HOMEASSISTANT_CLOSE) -from homeassistant.components import sun, mqtt, recorder +from homeassistant.components import mqtt, recorder from homeassistant.components.http.auth import auth_middleware from homeassistant.components.http.const import ( KEY_USE_X_FORWARDED_FOR, KEY_BANS_ENABLED, KEY_TRUSTED_NETWORKS) @@ -213,20 +212,6 @@ def fire_service_discovered(hass, service, info): }) -def ensure_sun_risen(hass): - """Trigger sun to rise if below horizon.""" - if sun.is_on(hass): - return - fire_time_changed(hass, sun.next_rising_utc(hass) + timedelta(seconds=10)) - - -def ensure_sun_set(hass): - """Trigger sun to set if above horizon.""" - if not sun.is_on(hass): - return - fire_time_changed(hass, sun.next_setting_utc(hass) + timedelta(seconds=10)) - - def load_fixture(filename): """Load a fixture.""" path = os.path.join(os.path.dirname(__file__), 'fixtures', filename) diff --git a/tests/components/automation/test_sun.py b/tests/components/automation/test_sun.py index 2341d22d633..ac1d7bc5acf 100644 --- a/tests/components/automation/test_sun.py +++ b/tests/components/automation/test_sun.py @@ -22,7 +22,8 @@ class TestAutomationSun(unittest.TestCase): """Setup things to be run when tests are started.""" self.hass = get_test_home_assistant() mock_component(self.hass, 'group') - mock_component(self.hass, 'sun') + setup_component(self.hass, sun.DOMAIN, { + sun.DOMAIN: {sun.CONF_ELEVATION: 0}}) self.calls = [] @@ -39,10 +40,6 @@ class TestAutomationSun(unittest.TestCase): def test_sunset_trigger(self): """Test the sunset trigger.""" - self.hass.states.set(sun.ENTITY_ID, sun.STATE_ABOVE_HORIZON, { - sun.STATE_ATTR_NEXT_SETTING: '2015-09-16T02:00:00Z', - }) - now = datetime(2015, 9, 15, 23, tzinfo=dt_util.UTC) trigger_time = datetime(2015, 9, 16, 2, tzinfo=dt_util.UTC) @@ -78,10 +75,6 @@ class TestAutomationSun(unittest.TestCase): def test_sunrise_trigger(self): """Test the sunrise trigger.""" - self.hass.states.set(sun.ENTITY_ID, sun.STATE_ABOVE_HORIZON, { - sun.STATE_ATTR_NEXT_RISING: '2015-09-16T14:00:00Z', - }) - now = datetime(2015, 9, 13, 23, tzinfo=dt_util.UTC) trigger_time = datetime(2015, 9, 16, 14, tzinfo=dt_util.UTC) @@ -105,10 +98,6 @@ class TestAutomationSun(unittest.TestCase): def test_sunset_trigger_with_offset(self): """Test the sunset trigger with offset.""" - self.hass.states.set(sun.ENTITY_ID, sun.STATE_ABOVE_HORIZON, { - sun.STATE_ATTR_NEXT_SETTING: '2015-09-16T02:00:00Z', - }) - now = datetime(2015, 9, 15, 23, tzinfo=dt_util.UTC) trigger_time = datetime(2015, 9, 16, 2, 30, tzinfo=dt_util.UTC) @@ -139,10 +128,6 @@ class TestAutomationSun(unittest.TestCase): def test_sunrise_trigger_with_offset(self): """Test the runrise trigger with offset.""" - self.hass.states.set(sun.ENTITY_ID, sun.STATE_ABOVE_HORIZON, { - sun.STATE_ATTR_NEXT_RISING: '2015-09-16T14:00:00Z', - }) - now = datetime(2015, 9, 13, 23, tzinfo=dt_util.UTC) trigger_time = datetime(2015, 9, 16, 13, 30, tzinfo=dt_util.UTC) @@ -167,10 +152,6 @@ class TestAutomationSun(unittest.TestCase): def test_if_action_before(self): """Test if action was before.""" - self.hass.states.set(sun.ENTITY_ID, sun.STATE_ABOVE_HORIZON, { - sun.STATE_ATTR_NEXT_RISING: '2015-09-16T14:00:00Z', - }) - setup_component(self.hass, automation.DOMAIN, { automation.DOMAIN: { 'trigger': { @@ -188,14 +169,14 @@ class TestAutomationSun(unittest.TestCase): }) now = datetime(2015, 9, 16, 15, tzinfo=dt_util.UTC) - with patch('homeassistant.util.dt.now', + with patch('homeassistant.util.dt.utcnow', return_value=now): self.hass.bus.fire('test_event') self.hass.block_till_done() self.assertEqual(0, len(self.calls)) now = datetime(2015, 9, 16, 10, tzinfo=dt_util.UTC) - with patch('homeassistant.util.dt.now', + with patch('homeassistant.util.dt.utcnow', return_value=now): self.hass.bus.fire('test_event') self.hass.block_till_done() @@ -203,10 +184,6 @@ class TestAutomationSun(unittest.TestCase): def test_if_action_after(self): """Test if action was after.""" - self.hass.states.set(sun.ENTITY_ID, sun.STATE_ABOVE_HORIZON, { - sun.STATE_ATTR_NEXT_RISING: '2015-09-16T14:00:00Z', - }) - setup_component(self.hass, automation.DOMAIN, { automation.DOMAIN: { 'trigger': { @@ -224,14 +201,14 @@ class TestAutomationSun(unittest.TestCase): }) now = datetime(2015, 9, 16, 13, tzinfo=dt_util.UTC) - with patch('homeassistant.util.dt.now', + with patch('homeassistant.util.dt.utcnow', return_value=now): self.hass.bus.fire('test_event') self.hass.block_till_done() self.assertEqual(0, len(self.calls)) now = datetime(2015, 9, 16, 15, tzinfo=dt_util.UTC) - with patch('homeassistant.util.dt.now', + with patch('homeassistant.util.dt.utcnow', return_value=now): self.hass.bus.fire('test_event') self.hass.block_till_done() @@ -239,10 +216,6 @@ class TestAutomationSun(unittest.TestCase): def test_if_action_before_with_offset(self): """Test if action was before offset.""" - self.hass.states.set(sun.ENTITY_ID, sun.STATE_ABOVE_HORIZON, { - sun.STATE_ATTR_NEXT_RISING: '2015-09-16T14:00:00Z', - }) - setup_component(self.hass, automation.DOMAIN, { automation.DOMAIN: { 'trigger': { @@ -260,15 +233,15 @@ class TestAutomationSun(unittest.TestCase): } }) - now = datetime(2015, 9, 16, 15, 1, tzinfo=dt_util.UTC) - with patch('homeassistant.util.dt.now', + now = datetime(2015, 9, 16, 14, 32, 44, tzinfo=dt_util.UTC) + with patch('homeassistant.util.dt.utcnow', return_value=now): self.hass.bus.fire('test_event') self.hass.block_till_done() self.assertEqual(0, len(self.calls)) - now = datetime(2015, 9, 16, 15, tzinfo=dt_util.UTC) - with patch('homeassistant.util.dt.now', + now = datetime(2015, 9, 16, 14, 32, 43, tzinfo=dt_util.UTC) + with patch('homeassistant.util.dt.utcnow', return_value=now): self.hass.bus.fire('test_event') self.hass.block_till_done() @@ -276,10 +249,6 @@ class TestAutomationSun(unittest.TestCase): def test_if_action_after_with_offset(self): """Test if action was after offset.""" - self.hass.states.set(sun.ENTITY_ID, sun.STATE_ABOVE_HORIZON, { - sun.STATE_ATTR_NEXT_RISING: '2015-09-16T14:00:00Z', - }) - setup_component(self.hass, automation.DOMAIN, { automation.DOMAIN: { 'trigger': { @@ -297,15 +266,15 @@ class TestAutomationSun(unittest.TestCase): } }) - now = datetime(2015, 9, 16, 14, 59, tzinfo=dt_util.UTC) - with patch('homeassistant.util.dt.now', + now = datetime(2015, 9, 16, 14, 32, 42, tzinfo=dt_util.UTC) + with patch('homeassistant.util.dt.utcnow', return_value=now): self.hass.bus.fire('test_event') self.hass.block_till_done() self.assertEqual(0, len(self.calls)) - now = datetime(2015, 9, 16, 15, tzinfo=dt_util.UTC) - with patch('homeassistant.util.dt.now', + now = datetime(2015, 9, 16, 14, 32, 43, tzinfo=dt_util.UTC) + with patch('homeassistant.util.dt.utcnow', return_value=now): self.hass.bus.fire('test_event') self.hass.block_till_done() @@ -313,11 +282,6 @@ class TestAutomationSun(unittest.TestCase): def test_if_action_before_and_after_during(self): """Test if action was before and after during.""" - self.hass.states.set(sun.ENTITY_ID, sun.STATE_ABOVE_HORIZON, { - sun.STATE_ATTR_NEXT_RISING: '2015-09-16T10:00:00Z', - sun.STATE_ATTR_NEXT_SETTING: '2015-09-16T15:00:00Z', - }) - setup_component(self.hass, automation.DOMAIN, { automation.DOMAIN: { 'trigger': { @@ -335,62 +299,22 @@ class TestAutomationSun(unittest.TestCase): } }) - now = datetime(2015, 9, 16, 9, 59, tzinfo=dt_util.UTC) - with patch('homeassistant.util.dt.now', + now = datetime(2015, 9, 16, 13, 8, 51, tzinfo=dt_util.UTC) + with patch('homeassistant.util.dt.utcnow', return_value=now): self.hass.bus.fire('test_event') self.hass.block_till_done() self.assertEqual(0, len(self.calls)) - now = datetime(2015, 9, 16, 15, 1, tzinfo=dt_util.UTC) - with patch('homeassistant.util.dt.now', + now = datetime(2015, 9, 17, 2, 25, 18, tzinfo=dt_util.UTC) + with patch('homeassistant.util.dt.utcnow', return_value=now): self.hass.bus.fire('test_event') self.hass.block_till_done() self.assertEqual(0, len(self.calls)) - now = datetime(2015, 9, 16, 12, tzinfo=dt_util.UTC) - with patch('homeassistant.util.dt.now', - return_value=now): - self.hass.bus.fire('test_event') - self.hass.block_till_done() - self.assertEqual(1, len(self.calls)) - - def test_if_action_after_different_tz(self): - """Test if action was after in a different timezone.""" - import pytz - - self.hass.states.set(sun.ENTITY_ID, sun.STATE_ABOVE_HORIZON, { - sun.STATE_ATTR_NEXT_SETTING: '2015-09-16T17:30:00Z', - }) - - setup_component(self.hass, automation.DOMAIN, { - automation.DOMAIN: { - 'trigger': { - 'platform': 'event', - 'event_type': 'test_event', - }, - 'condition': { - 'condition': 'sun', - 'after': 'sunset', - }, - 'action': { - 'service': 'test.automation' - } - } - }) - - # Before - now = datetime(2015, 9, 16, 17, tzinfo=pytz.timezone('US/Mountain')) - with patch('homeassistant.util.dt.now', - return_value=now): - self.hass.bus.fire('test_event') - self.hass.block_till_done() - self.assertEqual(0, len(self.calls)) - - # After - now = datetime(2015, 9, 16, 18, tzinfo=pytz.timezone('US/Mountain')) - with patch('homeassistant.util.dt.now', + now = datetime(2015, 9, 16, 16, tzinfo=dt_util.UTC) + with patch('homeassistant.util.dt.utcnow', return_value=now): self.hass.bus.fire('test_event') self.hass.block_till_done() diff --git a/tests/components/switch/test_flux.py b/tests/components/switch/test_flux.py index b42177a5f06..2422f0ea334 100644 --- a/tests/components/switch/test_flux.py +++ b/tests/components/switch/test_flux.py @@ -1,5 +1,4 @@ """The tests for the Flux switch platform.""" -from datetime import timedelta import unittest from unittest.mock import patch @@ -86,28 +85,30 @@ class TestSwitchFlux(unittest.TestCase): self.assertIsNone(state.attributes.get('xy_color')) self.assertIsNone(state.attributes.get('brightness')) - test_time = dt_util.now().replace(hour=10, minute=30, - second=0) - sunset_time = test_time.replace(hour=17, minute=0, - second=0) - sunrise_time = test_time.replace(hour=5, minute=0, - second=0) + timedelta(days=1) + test_time = dt_util.now().replace(hour=10, minute=30, second=0) + sunset_time = test_time.replace(hour=17, minute=0, second=0) + sunrise_time = test_time.replace(hour=5, minute=0, second=0) + + def event_date(hass, event, now=None): + if event == 'sunrise': + return sunrise_time + else: + return sunset_time + with patch('homeassistant.util.dt.now', return_value=test_time): - with patch('homeassistant.components.sun.next_rising', - return_value=sunrise_time): - with patch('homeassistant.components.sun.next_setting', - return_value=sunset_time): - assert setup_component(self.hass, switch.DOMAIN, { - switch.DOMAIN: { - 'platform': 'flux', - 'name': 'flux', - 'lights': [dev1.entity_id] - } - }) - turn_on_calls = mock_service( - self.hass, light.DOMAIN, SERVICE_TURN_ON) - fire_time_changed(self.hass, test_time) - self.hass.block_till_done() + with patch('homeassistant.helpers.sun.get_astral_event_date', + side_effect=event_date): + assert setup_component(self.hass, switch.DOMAIN, { + switch.DOMAIN: { + 'platform': 'flux', + 'name': 'flux', + 'lights': [dev1.entity_id] + } + }) + turn_on_calls = mock_service( + self.hass, light.DOMAIN, SERVICE_TURN_ON) + fire_time_changed(self.hass, test_time) + self.hass.block_till_done() self.assertEqual(0, len(turn_on_calls)) def test_flux_before_sunrise(self): @@ -126,30 +127,32 @@ class TestSwitchFlux(unittest.TestCase): self.assertIsNone(state.attributes.get('xy_color')) self.assertIsNone(state.attributes.get('brightness')) - test_time = dt_util.now().replace(hour=2, minute=30, - second=0) - sunset_time = test_time.replace(hour=17, minute=0, - second=0) - sunrise_time = test_time.replace(hour=5, minute=0, - second=0) + timedelta(days=1) + test_time = dt_util.now().replace(hour=2, minute=30, second=0) + sunset_time = test_time.replace(hour=17, minute=0, second=0) + sunrise_time = test_time.replace(hour=5, minute=0, second=0) + + def event_date(hass, event, now=None): + if event == 'sunrise': + return sunrise_time + else: + return sunset_time + with patch('homeassistant.util.dt.now', return_value=test_time): - with patch('homeassistant.components.sun.next_rising', - return_value=sunrise_time): - with patch('homeassistant.components.sun.next_setting', - return_value=sunset_time): - assert setup_component(self.hass, switch.DOMAIN, { - switch.DOMAIN: { - 'platform': 'flux', - 'name': 'flux', - 'lights': [dev1.entity_id] - } - }) - turn_on_calls = mock_service( - self.hass, light.DOMAIN, SERVICE_TURN_ON) - switch.turn_on(self.hass, 'switch.flux') - self.hass.block_till_done() - fire_time_changed(self.hass, test_time) - self.hass.block_till_done() + with patch('homeassistant.helpers.sun.get_astral_event_date', + side_effect=event_date): + assert setup_component(self.hass, switch.DOMAIN, { + switch.DOMAIN: { + 'platform': 'flux', + 'name': 'flux', + 'lights': [dev1.entity_id] + } + }) + turn_on_calls = mock_service( + self.hass, light.DOMAIN, SERVICE_TURN_ON) + switch.turn_on(self.hass, 'switch.flux') + self.hass.block_till_done() + fire_time_changed(self.hass, test_time) + self.hass.block_till_done() call = turn_on_calls[-1] self.assertEqual(call.data[light.ATTR_BRIGHTNESS], 119) self.assertEqual(call.data[light.ATTR_XY_COLOR], [0.591, 0.395]) @@ -173,28 +176,30 @@ class TestSwitchFlux(unittest.TestCase): test_time = dt_util.now().replace(hour=8, minute=30, second=0) sunset_time = test_time.replace(hour=17, minute=0, second=0) - sunrise_time = test_time.replace(hour=5, - minute=0, - second=0) + timedelta(days=1) + sunrise_time = test_time.replace(hour=5, minute=0, second=0) + + def event_date(hass, event, now=None): + if event == 'sunrise': + return sunrise_time + else: + return sunset_time with patch('homeassistant.util.dt.now', return_value=test_time): - with patch('homeassistant.components.sun.next_rising', - return_value=sunrise_time): - with patch('homeassistant.components.sun.next_setting', - return_value=sunset_time): - assert setup_component(self.hass, switch.DOMAIN, { - switch.DOMAIN: { - 'platform': 'flux', - 'name': 'flux', - 'lights': [dev1.entity_id] - } - }) - turn_on_calls = mock_service( - self.hass, light.DOMAIN, SERVICE_TURN_ON) - switch.turn_on(self.hass, 'switch.flux') - self.hass.block_till_done() - fire_time_changed(self.hass, test_time) - self.hass.block_till_done() + with patch('homeassistant.helpers.sun.get_astral_event_date', + side_effect=event_date): + assert setup_component(self.hass, switch.DOMAIN, { + switch.DOMAIN: { + 'platform': 'flux', + 'name': 'flux', + 'lights': [dev1.entity_id] + } + }) + turn_on_calls = mock_service( + self.hass, light.DOMAIN, SERVICE_TURN_ON) + switch.turn_on(self.hass, 'switch.flux') + self.hass.block_till_done() + fire_time_changed(self.hass, test_time) + self.hass.block_till_done() call = turn_on_calls[-1] self.assertEqual(call.data[light.ATTR_BRIGHTNESS], 180) self.assertEqual(call.data[light.ATTR_XY_COLOR], [0.431, 0.38]) @@ -218,28 +223,30 @@ class TestSwitchFlux(unittest.TestCase): test_time = dt_util.now().replace(hour=17, minute=30, second=0) sunset_time = test_time.replace(hour=17, minute=0, second=0) - sunrise_time = test_time.replace(hour=5, - minute=0, - second=0) + timedelta(days=1) + sunrise_time = test_time.replace(hour=5, minute=0, second=0) + + def event_date(hass, event, now=None): + if event == 'sunrise': + return sunrise_time + else: + return sunset_time with patch('homeassistant.util.dt.now', return_value=test_time): - with patch('homeassistant.components.sun.next_rising', - return_value=sunrise_time): - with patch('homeassistant.components.sun.next_setting', - return_value=sunset_time): - assert setup_component(self.hass, switch.DOMAIN, { - switch.DOMAIN: { - 'platform': 'flux', - 'name': 'flux', - 'lights': [dev1.entity_id] - } - }) - turn_on_calls = mock_service( - self.hass, light.DOMAIN, SERVICE_TURN_ON) - switch.turn_on(self.hass, 'switch.flux') - self.hass.block_till_done() - fire_time_changed(self.hass, test_time) - self.hass.block_till_done() + with patch('homeassistant.helpers.sun.get_astral_event_date', + side_effect=event_date): + assert setup_component(self.hass, switch.DOMAIN, { + switch.DOMAIN: { + 'platform': 'flux', + 'name': 'flux', + 'lights': [dev1.entity_id] + } + }) + turn_on_calls = mock_service( + self.hass, light.DOMAIN, SERVICE_TURN_ON) + switch.turn_on(self.hass, 'switch.flux') + self.hass.block_till_done() + fire_time_changed(self.hass, test_time) + self.hass.block_till_done() call = turn_on_calls[-1] self.assertEqual(call.data[light.ATTR_BRIGHTNESS], 153) self.assertEqual(call.data[light.ATTR_XY_COLOR], [0.496, 0.397]) @@ -263,28 +270,30 @@ class TestSwitchFlux(unittest.TestCase): test_time = dt_util.now().replace(hour=23, minute=30, second=0) sunset_time = test_time.replace(hour=17, minute=0, second=0) - sunrise_time = test_time.replace(hour=5, - minute=0, - second=0) + timedelta(days=1) + sunrise_time = test_time.replace(hour=5, minute=0, second=0) + + def event_date(hass, event, now=None): + if event == 'sunrise': + return sunrise_time + else: + return sunset_time with patch('homeassistant.util.dt.now', return_value=test_time): - with patch('homeassistant.components.sun.next_rising', - return_value=sunrise_time): - with patch('homeassistant.components.sun.next_setting', - return_value=sunset_time): - assert setup_component(self.hass, switch.DOMAIN, { - switch.DOMAIN: { - 'platform': 'flux', - 'name': 'flux', - 'lights': [dev1.entity_id] - } - }) - turn_on_calls = mock_service( - self.hass, light.DOMAIN, SERVICE_TURN_ON) - switch.turn_on(self.hass, 'switch.flux') - self.hass.block_till_done() - fire_time_changed(self.hass, test_time) - self.hass.block_till_done() + with patch('homeassistant.helpers.sun.get_astral_event_date', + side_effect=event_date): + assert setup_component(self.hass, switch.DOMAIN, { + switch.DOMAIN: { + 'platform': 'flux', + 'name': 'flux', + 'lights': [dev1.entity_id] + } + }) + turn_on_calls = mock_service( + self.hass, light.DOMAIN, SERVICE_TURN_ON) + switch.turn_on(self.hass, 'switch.flux') + self.hass.block_till_done() + fire_time_changed(self.hass, test_time) + self.hass.block_till_done() call = turn_on_calls[-1] self.assertEqual(call.data[light.ATTR_BRIGHTNESS], 119) self.assertEqual(call.data[light.ATTR_XY_COLOR], [0.591, 0.395]) @@ -308,30 +317,32 @@ class TestSwitchFlux(unittest.TestCase): test_time = dt_util.now().replace(hour=17, minute=30, second=0) sunset_time = test_time.replace(hour=17, minute=0, second=0) - sunrise_time = test_time.replace(hour=5, - minute=0, - second=0) + timedelta(days=1) + sunrise_time = test_time.replace(hour=5, minute=0, second=0) + + def event_date(hass, event, now=None): + if event == 'sunrise': + return sunrise_time + else: + return sunset_time with patch('homeassistant.util.dt.now', return_value=test_time): - with patch('homeassistant.components.sun.next_rising', - return_value=sunrise_time): - with patch('homeassistant.components.sun.next_setting', - return_value=sunset_time): - assert setup_component(self.hass, switch.DOMAIN, { - switch.DOMAIN: { - 'platform': 'flux', - 'name': 'flux', - 'lights': [dev1.entity_id], - 'start_time': '6:00', - 'stop_time': '23:30' - } - }) - turn_on_calls = mock_service( - self.hass, light.DOMAIN, SERVICE_TURN_ON) - switch.turn_on(self.hass, 'switch.flux') - self.hass.block_till_done() - fire_time_changed(self.hass, test_time) - self.hass.block_till_done() + with patch('homeassistant.helpers.sun.get_astral_event_date', + side_effect=event_date): + assert setup_component(self.hass, switch.DOMAIN, { + switch.DOMAIN: { + 'platform': 'flux', + 'name': 'flux', + 'lights': [dev1.entity_id], + 'start_time': '6:00', + 'stop_time': '23:30' + } + }) + turn_on_calls = mock_service( + self.hass, light.DOMAIN, SERVICE_TURN_ON) + switch.turn_on(self.hass, 'switch.flux') + self.hass.block_till_done() + fire_time_changed(self.hass, test_time) + self.hass.block_till_done() call = turn_on_calls[-1] self.assertEqual(call.data[light.ATTR_BRIGHTNESS], 154) self.assertEqual(call.data[light.ATTR_XY_COLOR], [0.494, 0.397]) @@ -355,30 +366,32 @@ class TestSwitchFlux(unittest.TestCase): test_time = dt_util.now().replace(hour=17, minute=30, second=0) sunset_time = test_time.replace(hour=17, minute=0, second=0) - sunrise_time = test_time.replace(hour=5, - minute=0, - second=0) + timedelta(days=1) + sunrise_time = test_time.replace(hour=5, minute=0, second=0) + + def event_date(hass, event, now=None): + if event == 'sunrise': + return sunrise_time + else: + return sunset_time with patch('homeassistant.util.dt.now', return_value=test_time): - with patch('homeassistant.components.sun.next_rising', - return_value=sunrise_time): - with patch('homeassistant.components.sun.next_setting', - return_value=sunset_time): - assert setup_component(self.hass, switch.DOMAIN, { - switch.DOMAIN: { - 'platform': 'flux', - 'name': 'flux', - 'lights': [dev1.entity_id], - 'start_colortemp': '1000', - 'stop_colortemp': '6000' - } - }) - turn_on_calls = mock_service( - self.hass, light.DOMAIN, SERVICE_TURN_ON) - switch.turn_on(self.hass, 'switch.flux') - self.hass.block_till_done() - fire_time_changed(self.hass, test_time) - self.hass.block_till_done() + with patch('homeassistant.helpers.sun.get_astral_event_date', + side_effect=event_date): + assert setup_component(self.hass, switch.DOMAIN, { + switch.DOMAIN: { + 'platform': 'flux', + 'name': 'flux', + 'lights': [dev1.entity_id], + 'start_colortemp': '1000', + 'stop_colortemp': '6000' + } + }) + turn_on_calls = mock_service( + self.hass, light.DOMAIN, SERVICE_TURN_ON) + switch.turn_on(self.hass, 'switch.flux') + self.hass.block_till_done() + fire_time_changed(self.hass, test_time) + self.hass.block_till_done() call = turn_on_calls[-1] self.assertEqual(call.data[light.ATTR_BRIGHTNESS], 167) self.assertEqual(call.data[light.ATTR_XY_COLOR], [0.461, 0.389]) @@ -402,29 +415,31 @@ class TestSwitchFlux(unittest.TestCase): test_time = dt_util.now().replace(hour=17, minute=30, second=0) sunset_time = test_time.replace(hour=17, minute=0, second=0) - sunrise_time = test_time.replace(hour=5, - minute=0, - second=0) + timedelta(days=1) + sunrise_time = test_time.replace(hour=5, minute=0, second=0) + + def event_date(hass, event, now=None): + if event == 'sunrise': + return sunrise_time + else: + return sunset_time with patch('homeassistant.util.dt.now', return_value=test_time): - with patch('homeassistant.components.sun.next_rising', - return_value=sunrise_time): - with patch('homeassistant.components.sun.next_setting', - return_value=sunset_time): - assert setup_component(self.hass, switch.DOMAIN, { - switch.DOMAIN: { - 'platform': 'flux', - 'name': 'flux', - 'lights': [dev1.entity_id], - 'brightness': 255 - } - }) - turn_on_calls = mock_service( - self.hass, light.DOMAIN, SERVICE_TURN_ON) - switch.turn_on(self.hass, 'switch.flux') - self.hass.block_till_done() - fire_time_changed(self.hass, test_time) - self.hass.block_till_done() + with patch('homeassistant.helpers.sun.get_astral_event_date', + side_effect=event_date): + assert setup_component(self.hass, switch.DOMAIN, { + switch.DOMAIN: { + 'platform': 'flux', + 'name': 'flux', + 'lights': [dev1.entity_id], + 'brightness': 255 + } + }) + turn_on_calls = mock_service( + self.hass, light.DOMAIN, SERVICE_TURN_ON) + switch.turn_on(self.hass, 'switch.flux') + self.hass.block_till_done() + fire_time_changed(self.hass, test_time) + self.hass.block_till_done() call = turn_on_calls[-1] self.assertEqual(call.data[light.ATTR_BRIGHTNESS], 255) self.assertEqual(call.data[light.ATTR_XY_COLOR], [0.496, 0.397]) @@ -460,30 +475,34 @@ class TestSwitchFlux(unittest.TestCase): test_time = dt_util.now().replace(hour=12, minute=0, second=0) sunset_time = test_time.replace(hour=17, minute=0, second=0) - sunrise_time = test_time.replace(hour=5, - minute=0, - second=0) + timedelta(days=1) + sunrise_time = test_time.replace(hour=5, minute=0, second=0) + + def event_date(hass, event, now=None): + if event == 'sunrise': + print('sunrise {}'.format(sunrise_time)) + return sunrise_time + else: + print('sunset {}'.format(sunset_time)) + return sunset_time with patch('homeassistant.util.dt.now', return_value=test_time): - with patch('homeassistant.components.sun.next_rising', - return_value=sunrise_time): - with patch('homeassistant.components.sun.next_setting', - return_value=sunset_time): - assert setup_component(self.hass, switch.DOMAIN, { - switch.DOMAIN: { - 'platform': 'flux', - 'name': 'flux', - 'lights': [dev1.entity_id, - dev2.entity_id, - dev3.entity_id] - } - }) - turn_on_calls = mock_service( - self.hass, light.DOMAIN, SERVICE_TURN_ON) - switch.turn_on(self.hass, 'switch.flux') - self.hass.block_till_done() - fire_time_changed(self.hass, test_time) - self.hass.block_till_done() + with patch('homeassistant.helpers.sun.get_astral_event_date', + side_effect=event_date): + assert setup_component(self.hass, switch.DOMAIN, { + switch.DOMAIN: { + 'platform': 'flux', + 'name': 'flux', + 'lights': [dev1.entity_id, + dev2.entity_id, + dev3.entity_id] + } + }) + turn_on_calls = mock_service( + self.hass, light.DOMAIN, SERVICE_TURN_ON) + switch.turn_on(self.hass, 'switch.flux') + self.hass.block_till_done() + fire_time_changed(self.hass, test_time) + self.hass.block_till_done() call = turn_on_calls[-1] self.assertEqual(call.data[light.ATTR_BRIGHTNESS], 171) self.assertEqual(call.data[light.ATTR_XY_COLOR], [0.452, 0.386]) @@ -511,28 +530,30 @@ class TestSwitchFlux(unittest.TestCase): test_time = dt_util.now().replace(hour=8, minute=30, second=0) sunset_time = test_time.replace(hour=17, minute=0, second=0) - sunrise_time = test_time.replace(hour=5, - minute=0, - second=0) + timedelta(days=1) + sunrise_time = test_time.replace(hour=5, minute=0, second=0) + + def event_date(hass, event, now=None): + if event == 'sunrise': + return sunrise_time + else: + return sunset_time with patch('homeassistant.util.dt.now', return_value=test_time): - with patch('homeassistant.components.sun.next_rising', - return_value=sunrise_time): - with patch('homeassistant.components.sun.next_setting', - return_value=sunset_time): - assert setup_component(self.hass, switch.DOMAIN, { - switch.DOMAIN: { - 'platform': 'flux', - 'name': 'flux', - 'lights': [dev1.entity_id], - 'mode': 'mired' - } - }) - turn_on_calls = mock_service( - self.hass, light.DOMAIN, SERVICE_TURN_ON) - switch.turn_on(self.hass, 'switch.flux') - self.hass.block_till_done() - fire_time_changed(self.hass, test_time) - self.hass.block_till_done() + with patch('homeassistant.helpers.sun.get_astral_event_date', + side_effect=event_date): + assert setup_component(self.hass, switch.DOMAIN, { + switch.DOMAIN: { + 'platform': 'flux', + 'name': 'flux', + 'lights': [dev1.entity_id], + 'mode': 'mired' + } + }) + turn_on_calls = mock_service( + self.hass, light.DOMAIN, SERVICE_TURN_ON) + switch.turn_on(self.hass, 'switch.flux') + self.hass.block_till_done() + fire_time_changed(self.hass, test_time) + self.hass.block_till_done() call = turn_on_calls[-1] self.assertEqual(call.data[light.ATTR_COLOR_TEMP], 269) diff --git a/tests/components/test_device_sun_light_trigger.py b/tests/components/test_device_sun_light_trigger.py index 2d2f7313199..5cd85a16a7a 100644 --- a/tests/components/test_device_sun_light_trigger.py +++ b/tests/components/test_device_sun_light_trigger.py @@ -1,17 +1,19 @@ """The tests device sun light trigger component.""" # pylint: disable=protected-access +from datetime import datetime import os import unittest +from unittest.mock import patch from homeassistant.setup import setup_component import homeassistant.loader as loader from homeassistant.const import CONF_PLATFORM, STATE_HOME, STATE_NOT_HOME from homeassistant.components import ( - device_tracker, light, sun, device_sun_light_trigger) + device_tracker, light, device_sun_light_trigger) +from homeassistant.util import dt as dt_util from tests.common import ( - get_test_config_dir, get_test_home_assistant, ensure_sun_risen, - ensure_sun_set) + get_test_config_dir, get_test_home_assistant, fire_time_changed) KNOWN_DEV_YAML_PATH = os.path.join(get_test_config_dir(), @@ -61,26 +63,26 @@ class TestDeviceSunLightTrigger(unittest.TestCase): light.DOMAIN: {CONF_PLATFORM: 'test'} })) - self.assertTrue(setup_component(self.hass, sun.DOMAIN, { - sun.DOMAIN: {sun.CONF_ELEVATION: 0}})) - def tearDown(self): # pylint: disable=invalid-name """Stop everything that was started.""" self.hass.stop() def test_lights_on_when_sun_sets(self): """Test lights go on when there is someone home and the sun sets.""" - self.assertTrue(setup_component( - self.hass, device_sun_light_trigger.DOMAIN, { - device_sun_light_trigger.DOMAIN: {}})) + test_time = datetime(2017, 4, 5, 1, 2, 3, tzinfo=dt_util.UTC) + with patch('homeassistant.util.dt.utcnow', return_value=test_time): + self.assertTrue(setup_component( + self.hass, device_sun_light_trigger.DOMAIN, { + device_sun_light_trigger.DOMAIN: {}})) - ensure_sun_risen(self.hass) light.turn_off(self.hass) self.hass.block_till_done() - ensure_sun_set(self.hass) - self.hass.block_till_done() + test_time = test_time.replace(hour=3) + with patch('homeassistant.util.dt.utcnow', return_value=test_time): + fire_time_changed(self.hass, test_time) + self.hass.block_till_done() self.assertTrue(light.is_on(self.hass)) @@ -105,17 +107,17 @@ class TestDeviceSunLightTrigger(unittest.TestCase): def test_lights_turn_on_when_coming_home_after_sun_set(self): \ # pylint: disable=invalid-name """Test lights turn on when coming home after sun set.""" - light.turn_off(self.hass) - ensure_sun_set(self.hass) + test_time = datetime(2017, 4, 5, 3, 2, 3, tzinfo=dt_util.UTC) + with patch('homeassistant.util.dt.utcnow', return_value=test_time): + light.turn_off(self.hass) + self.hass.block_till_done() - self.hass.block_till_done() + self.assertTrue(setup_component( + self.hass, device_sun_light_trigger.DOMAIN, { + device_sun_light_trigger.DOMAIN: {}})) - self.assertTrue(setup_component( - self.hass, device_sun_light_trigger.DOMAIN, { - device_sun_light_trigger.DOMAIN: {}})) + self.hass.states.set( + device_tracker.ENTITY_ID_FORMAT.format('device_2'), STATE_HOME) - self.hass.states.set( - device_tracker.ENTITY_ID_FORMAT.format('device_2'), STATE_HOME) - - self.hass.block_till_done() + self.hass.block_till_done() self.assertTrue(light.is_on(self.hass)) diff --git a/tests/components/test_sun.py b/tests/components/test_sun.py index 659e4b1a43d..d5a4ecfcb81 100644 --- a/tests/components/test_sun.py +++ b/tests/components/test_sun.py @@ -24,118 +24,111 @@ class TestSun(unittest.TestCase): """Stop everything that was started.""" self.hass.stop() - def test_is_on(self): - """Test is_on method.""" - self.hass.states.set(sun.ENTITY_ID, sun.STATE_ABOVE_HORIZON) - self.assertTrue(sun.is_on(self.hass)) - self.hass.states.set(sun.ENTITY_ID, sun.STATE_BELOW_HORIZON) - self.assertFalse(sun.is_on(self.hass)) - def test_setting_rising(self): """Test retrieving sun setting and rising.""" - setup_component(self.hass, sun.DOMAIN, { - sun.DOMAIN: {sun.CONF_ELEVATION: 0}}) + utc_now = datetime(2016, 11, 1, 8, 0, 0, tzinfo=dt_util.UTC) + with patch('homeassistant.helpers.condition.dt_util.utcnow', + return_value=utc_now): + setup_component(self.hass, sun.DOMAIN, { + sun.DOMAIN: {sun.CONF_ELEVATION: 0}}) + + self.hass.block_till_done() + state = self.hass.states.get(sun.ENTITY_ID) from astral import Astral astral = Astral() - utc_now = dt_util.utcnow() + utc_today = utc_now.date() latitude = self.hass.config.latitude longitude = self.hass.config.longitude mod = -1 while True: - next_dawn = (astral.dawn_utc(utc_now + - timedelta(days=mod), latitude, longitude)) + next_dawn = (astral.dawn_utc( + utc_today + timedelta(days=mod), latitude, longitude)) if next_dawn > utc_now: break mod += 1 mod = -1 while True: - next_dusk = (astral.dusk_utc(utc_now + - timedelta(days=mod), latitude, longitude)) + next_dusk = (astral.dusk_utc( + utc_today + timedelta(days=mod), latitude, longitude)) if next_dusk > utc_now: break mod += 1 mod = -1 while True: - next_midnight = (astral.solar_midnight_utc(utc_now + - timedelta(days=mod), longitude)) + next_midnight = (astral.solar_midnight_utc( + utc_today + timedelta(days=mod), longitude)) if next_midnight > utc_now: break mod += 1 mod = -1 while True: - next_noon = (astral.solar_noon_utc(utc_now + - timedelta(days=mod), longitude)) + next_noon = (astral.solar_noon_utc( + utc_today + timedelta(days=mod), longitude)) if next_noon > utc_now: break mod += 1 mod = -1 while True: - next_rising = (astral.sunrise_utc(utc_now + - timedelta(days=mod), latitude, longitude)) + next_rising = (astral.sunrise_utc( + utc_today + timedelta(days=mod), latitude, longitude)) if next_rising > utc_now: break mod += 1 mod = -1 while True: - next_setting = (astral.sunset_utc(utc_now + - timedelta(days=mod), latitude, longitude)) + next_setting = (astral.sunset_utc( + utc_today + timedelta(days=mod), latitude, longitude)) if next_setting > utc_now: break mod += 1 - self.assertEqual(next_dawn, sun.next_dawn_utc(self.hass)) - self.assertEqual(next_dusk, sun.next_dusk_utc(self.hass)) - self.assertEqual(next_midnight, sun.next_midnight_utc(self.hass)) - self.assertEqual(next_noon, sun.next_noon_utc(self.hass)) - self.assertEqual(next_rising, sun.next_rising_utc(self.hass)) - self.assertEqual(next_setting, sun.next_setting_utc(self.hass)) - - # Point it at a state without the proper attributes - self.hass.states.set(sun.ENTITY_ID, sun.STATE_ABOVE_HORIZON) - self.assertIsNone(sun.next_dawn(self.hass)) - self.assertIsNone(sun.next_dusk(self.hass)) - self.assertIsNone(sun.next_midnight(self.hass)) - self.assertIsNone(sun.next_noon(self.hass)) - self.assertIsNone(sun.next_rising(self.hass)) - self.assertIsNone(sun.next_setting(self.hass)) - - # Point it at a non-existing state - self.assertIsNone(sun.next_dawn(self.hass, 'non.existing')) - self.assertIsNone(sun.next_dusk(self.hass, 'non.existing')) - self.assertIsNone(sun.next_midnight(self.hass, 'non.existing')) - self.assertIsNone(sun.next_noon(self.hass, 'non.existing')) - self.assertIsNone(sun.next_rising(self.hass, 'non.existing')) - self.assertIsNone(sun.next_setting(self.hass, 'non.existing')) + self.assertEqual(next_dawn, dt_util.parse_datetime( + state.attributes[sun.STATE_ATTR_NEXT_DAWN])) + self.assertEqual(next_dusk, dt_util.parse_datetime( + state.attributes[sun.STATE_ATTR_NEXT_DUSK])) + self.assertEqual(next_midnight, dt_util.parse_datetime( + state.attributes[sun.STATE_ATTR_NEXT_MIDNIGHT])) + self.assertEqual(next_noon, dt_util.parse_datetime( + state.attributes[sun.STATE_ATTR_NEXT_NOON])) + self.assertEqual(next_rising, dt_util.parse_datetime( + state.attributes[sun.STATE_ATTR_NEXT_RISING])) + self.assertEqual(next_setting, dt_util.parse_datetime( + state.attributes[sun.STATE_ATTR_NEXT_SETTING])) def test_state_change(self): """Test if the state changes at next setting/rising.""" - setup_component(self.hass, sun.DOMAIN, { - sun.DOMAIN: {sun.CONF_ELEVATION: 0}}) + now = datetime(2016, 6, 1, 8, 0, 0, tzinfo=dt_util.UTC) + with patch('homeassistant.helpers.condition.dt_util.utcnow', + return_value=now): + setup_component(self.hass, sun.DOMAIN, { + sun.DOMAIN: {sun.CONF_ELEVATION: 0}}) - if sun.is_on(self.hass): - test_state = sun.STATE_BELOW_HORIZON - test_time = sun.next_setting(self.hass) - else: - test_state = sun.STATE_ABOVE_HORIZON - test_time = sun.next_rising(self.hass) + self.hass.block_till_done() + test_time = dt_util.parse_datetime( + self.hass.states.get(sun.ENTITY_ID) + .attributes[sun.STATE_ATTR_NEXT_RISING]) self.assertIsNotNone(test_time) + self.assertEqual(sun.STATE_BELOW_HORIZON, + self.hass.states.get(sun.ENTITY_ID).state) + self.hass.bus.fire(ha.EVENT_TIME_CHANGED, {ha.ATTR_NOW: test_time + timedelta(seconds=5)}) self.hass.block_till_done() - self.assertEqual(test_state, self.hass.states.get(sun.ENTITY_ID).state) + self.assertEqual(sun.STATE_ABOVE_HORIZON, + self.hass.states.get(sun.ENTITY_ID).state) def test_norway_in_june(self): """Test location in Norway where the sun doesn't set in summer.""" @@ -150,9 +143,11 @@ class TestSun(unittest.TestCase): sun.DOMAIN: {sun.CONF_ELEVATION: 0}}) state = self.hass.states.get(sun.ENTITY_ID) - assert state is not None - assert sun.next_rising_utc(self.hass) == \ + + assert dt_util.parse_datetime( + state.attributes[sun.STATE_ATTR_NEXT_RISING]) == \ datetime(2016, 7, 25, 23, 23, 39, tzinfo=dt_util.UTC) - assert sun.next_setting_utc(self.hass) == \ + assert dt_util.parse_datetime( + state.attributes[sun.STATE_ATTR_NEXT_SETTING]) == \ datetime(2016, 7, 26, 22, 19, 1, tzinfo=dt_util.UTC) diff --git a/tests/helpers/test_event.py b/tests/helpers/test_event.py index ac60aae3fab..37ff8ba297e 100644 --- a/tests/helpers/test_event.py +++ b/tests/helpers/test_event.py @@ -25,6 +25,7 @@ from homeassistant.components import sun import homeassistant.util.dt as dt_util from tests.common import get_test_home_assistant +from unittest.mock import patch class TestEventHelpers(unittest.TestCase): @@ -302,24 +303,27 @@ class TestEventHelpers(unittest.TestCase): # Get next sunrise/sunset astral = Astral() - utc_now = dt_util.utcnow() + utc_now = datetime(2014, 5, 24, 12, 0, 0, tzinfo=dt_util.UTC) + utc_today = utc_now.date() mod = -1 while True: - next_rising = (astral.sunrise_utc(utc_now + - timedelta(days=mod), latitude, longitude)) + next_rising = (astral.sunrise_utc( + utc_today + timedelta(days=mod), latitude, longitude)) if next_rising > utc_now: break mod += 1 # Track sunrise runs = [] - unsub = track_sunrise(self.hass, lambda: runs.append(1)) + with patch('homeassistant.util.dt.utcnow', return_value=utc_now): + unsub = track_sunrise(self.hass, lambda: runs.append(1)) offset_runs = [] offset = timedelta(minutes=30) - unsub2 = track_sunrise(self.hass, lambda: offset_runs.append(1), - offset) + with patch('homeassistant.util.dt.utcnow', return_value=utc_now): + unsub2 = track_sunrise(self.hass, lambda: offset_runs.append(1), + offset) # run tests self._send_time_changed(next_rising - offset) @@ -334,7 +338,7 @@ class TestEventHelpers(unittest.TestCase): self._send_time_changed(next_rising + offset) self.hass.block_till_done() - self.assertEqual(2, len(runs)) + self.assertEqual(1, len(runs)) self.assertEqual(1, len(offset_runs)) unsub() @@ -342,7 +346,7 @@ class TestEventHelpers(unittest.TestCase): self._send_time_changed(next_rising + offset) self.hass.block_till_done() - self.assertEqual(2, len(runs)) + self.assertEqual(1, len(runs)) self.assertEqual(1, len(offset_runs)) def test_track_sunset(self): @@ -358,23 +362,27 @@ class TestEventHelpers(unittest.TestCase): # Get next sunrise/sunset astral = Astral() - utc_now = dt_util.utcnow() + utc_now = datetime(2014, 5, 24, 12, 0, 0, tzinfo=dt_util.UTC) + utc_today = utc_now.date() mod = -1 while True: - next_setting = (astral.sunset_utc(utc_now + - timedelta(days=mod), latitude, longitude)) + next_setting = (astral.sunset_utc( + utc_today + timedelta(days=mod), latitude, longitude)) if next_setting > utc_now: break mod += 1 # Track sunset runs = [] - unsub = track_sunset(self.hass, lambda: runs.append(1)) + with patch('homeassistant.util.dt.utcnow', return_value=utc_now): + unsub = track_sunset(self.hass, lambda: runs.append(1)) offset_runs = [] offset = timedelta(minutes=30) - unsub2 = track_sunset(self.hass, lambda: offset_runs.append(1), offset) + with patch('homeassistant.util.dt.utcnow', return_value=utc_now): + unsub2 = track_sunset( + self.hass, lambda: offset_runs.append(1), offset) # Run tests self._send_time_changed(next_setting - offset) @@ -389,7 +397,7 @@ class TestEventHelpers(unittest.TestCase): self._send_time_changed(next_setting + offset) self.hass.block_till_done() - self.assertEqual(2, len(runs)) + self.assertEqual(1, len(runs)) self.assertEqual(1, len(offset_runs)) unsub() @@ -397,7 +405,7 @@ class TestEventHelpers(unittest.TestCase): self._send_time_changed(next_setting + offset) self.hass.block_till_done() - self.assertEqual(2, len(runs)) + self.assertEqual(1, len(runs)) self.assertEqual(1, len(offset_runs)) def _send_time_changed(self, now): diff --git a/tests/helpers/test_sun.py b/tests/helpers/test_sun.py new file mode 100644 index 00000000000..2cfe28e5178 --- /dev/null +++ b/tests/helpers/test_sun.py @@ -0,0 +1,227 @@ +"""The tests for the Sun helpers.""" +# pylint: disable=protected-access +import unittest +from unittest.mock import patch +from datetime import timedelta, datetime + +import homeassistant.util.dt as dt_util +import homeassistant.helpers.sun as sun + +from tests.common import get_test_home_assistant + + +# pylint: disable=invalid-name +class TestSun(unittest.TestCase): + """Test the sun helpers.""" + + def setUp(self): + """Setup things to be run when tests are started.""" + self.hass = get_test_home_assistant() + + def tearDown(self): + """Stop everything that was started.""" + self.hass.stop() + + def test_next_events(self): + """Test retrieving next sun events.""" + utc_now = datetime(2016, 11, 1, 8, 0, 0, tzinfo=dt_util.UTC) + from astral import Astral + + astral = Astral() + utc_today = utc_now.date() + + latitude = self.hass.config.latitude + longitude = self.hass.config.longitude + + mod = -1 + while True: + next_dawn = (astral.dawn_utc( + utc_today + timedelta(days=mod), latitude, longitude)) + if next_dawn > utc_now: + break + mod += 1 + + mod = -1 + while True: + next_dusk = (astral.dusk_utc( + utc_today + timedelta(days=mod), latitude, longitude)) + if next_dusk > utc_now: + break + mod += 1 + + mod = -1 + while True: + next_midnight = (astral.solar_midnight_utc( + utc_today + timedelta(days=mod), longitude)) + if next_midnight > utc_now: + break + mod += 1 + + mod = -1 + while True: + next_noon = (astral.solar_noon_utc( + utc_today + timedelta(days=mod), longitude)) + if next_noon > utc_now: + break + mod += 1 + + mod = -1 + while True: + next_rising = (astral.sunrise_utc( + utc_today + timedelta(days=mod), latitude, longitude)) + if next_rising > utc_now: + break + mod += 1 + + mod = -1 + while True: + next_setting = (astral.sunset_utc( + utc_today + timedelta(days=mod), latitude, longitude)) + if next_setting > utc_now: + break + mod += 1 + + with patch('homeassistant.helpers.condition.dt_util.utcnow', + return_value=utc_now): + self.assertEqual(next_dawn, sun.get_astral_event_next( + self.hass, 'dawn')) + self.assertEqual(next_dusk, sun.get_astral_event_next( + self.hass, 'dusk')) + self.assertEqual(next_midnight, sun.get_astral_event_next( + self.hass, 'solar_midnight')) + self.assertEqual(next_noon, sun.get_astral_event_next( + self.hass, 'solar_noon')) + self.assertEqual(next_rising, sun.get_astral_event_next( + self.hass, 'sunrise')) + self.assertEqual(next_setting, sun.get_astral_event_next( + self.hass, 'sunset')) + + def test_date_events(self): + """Test retrieving next sun events.""" + utc_now = datetime(2016, 11, 1, 8, 0, 0, tzinfo=dt_util.UTC) + from astral import Astral + + astral = Astral() + utc_today = utc_now.date() + + latitude = self.hass.config.latitude + longitude = self.hass.config.longitude + + dawn = astral.dawn_utc(utc_today, latitude, longitude) + dusk = astral.dusk_utc(utc_today, latitude, longitude) + midnight = astral.solar_midnight_utc(utc_today, longitude) + noon = astral.solar_noon_utc(utc_today, longitude) + sunrise = astral.sunrise_utc(utc_today, latitude, longitude) + sunset = astral.sunset_utc(utc_today, latitude, longitude) + + self.assertEqual(dawn, sun.get_astral_event_date( + self.hass, 'dawn', utc_today)) + self.assertEqual(dusk, sun.get_astral_event_date( + self.hass, 'dusk', utc_today)) + self.assertEqual(midnight, sun.get_astral_event_date( + self.hass, 'solar_midnight', utc_today)) + self.assertEqual(noon, sun.get_astral_event_date( + self.hass, 'solar_noon', utc_today)) + self.assertEqual(sunrise, sun.get_astral_event_date( + self.hass, 'sunrise', utc_today)) + self.assertEqual(sunset, sun.get_astral_event_date( + self.hass, 'sunset', utc_today)) + + def test_date_events_default_date(self): + """Test retrieving next sun events.""" + utc_now = datetime(2016, 11, 1, 8, 0, 0, tzinfo=dt_util.UTC) + from astral import Astral + + astral = Astral() + utc_today = utc_now.date() + + latitude = self.hass.config.latitude + longitude = self.hass.config.longitude + + dawn = astral.dawn_utc(utc_today, latitude, longitude) + dusk = astral.dusk_utc(utc_today, latitude, longitude) + midnight = astral.solar_midnight_utc(utc_today, longitude) + noon = astral.solar_noon_utc(utc_today, longitude) + sunrise = astral.sunrise_utc(utc_today, latitude, longitude) + sunset = astral.sunset_utc(utc_today, latitude, longitude) + + with patch('homeassistant.util.dt.now', return_value=utc_now): + self.assertEqual(dawn, sun.get_astral_event_date( + self.hass, 'dawn', utc_today)) + self.assertEqual(dusk, sun.get_astral_event_date( + self.hass, 'dusk', utc_today)) + self.assertEqual(midnight, sun.get_astral_event_date( + self.hass, 'solar_midnight', utc_today)) + self.assertEqual(noon, sun.get_astral_event_date( + self.hass, 'solar_noon', utc_today)) + self.assertEqual(sunrise, sun.get_astral_event_date( + self.hass, 'sunrise', utc_today)) + self.assertEqual(sunset, sun.get_astral_event_date( + self.hass, 'sunset', utc_today)) + + def test_date_events_accepts_datetime(self): + """Test retrieving next sun events.""" + utc_now = datetime(2016, 11, 1, 8, 0, 0, tzinfo=dt_util.UTC) + from astral import Astral + + astral = Astral() + utc_today = utc_now.date() + + latitude = self.hass.config.latitude + longitude = self.hass.config.longitude + + dawn = astral.dawn_utc(utc_today, latitude, longitude) + dusk = astral.dusk_utc(utc_today, latitude, longitude) + midnight = astral.solar_midnight_utc(utc_today, longitude) + noon = astral.solar_noon_utc(utc_today, longitude) + sunrise = astral.sunrise_utc(utc_today, latitude, longitude) + sunset = astral.sunset_utc(utc_today, latitude, longitude) + + self.assertEqual(dawn, sun.get_astral_event_date( + self.hass, 'dawn', utc_now)) + self.assertEqual(dusk, sun.get_astral_event_date( + self.hass, 'dusk', utc_now)) + self.assertEqual(midnight, sun.get_astral_event_date( + self.hass, 'solar_midnight', utc_now)) + self.assertEqual(noon, sun.get_astral_event_date( + self.hass, 'solar_noon', utc_now)) + self.assertEqual(sunrise, sun.get_astral_event_date( + self.hass, 'sunrise', utc_now)) + self.assertEqual(sunset, sun.get_astral_event_date( + self.hass, 'sunset', utc_now)) + + def test_is_up(self): + """Test retrieving next sun events.""" + utc_now = datetime(2016, 11, 1, 12, 0, 0, tzinfo=dt_util.UTC) + with patch('homeassistant.helpers.condition.dt_util.utcnow', + return_value=utc_now): + self.assertFalse(sun.is_up(self.hass)) + + utc_now = datetime(2016, 11, 1, 18, 0, 0, tzinfo=dt_util.UTC) + with patch('homeassistant.helpers.condition.dt_util.utcnow', + return_value=utc_now): + self.assertTrue(sun.is_up(self.hass)) + + def test_norway_in_june(self): + """Test location in Norway where the sun doesn't set in summer.""" + self.hass.config.latitude = 69.6 + self.hass.config.longitude = 18.8 + + june = datetime(2016, 6, 1, tzinfo=dt_util.UTC) + + print(sun.get_astral_event_date(self.hass, 'sunrise', + datetime(2017, 7, 25))) + print(sun.get_astral_event_date(self.hass, 'sunset', + datetime(2017, 7, 25))) + + print(sun.get_astral_event_date(self.hass, 'sunrise', + datetime(2017, 7, 26))) + print(sun.get_astral_event_date(self.hass, 'sunset', + datetime(2017, 7, 26))) + + assert sun.get_astral_event_next(self.hass, 'sunrise', june) == \ + datetime(2016, 7, 25, 23, 23, 39, tzinfo=dt_util.UTC) + assert sun.get_astral_event_next(self.hass, 'sunset', june) == \ + datetime(2016, 7, 26, 22, 19, 1, tzinfo=dt_util.UTC) + assert sun.get_astral_event_date(self.hass, 'sunrise', june) is None + assert sun.get_astral_event_date(self.hass, 'sunset', june) is None