Refactor sun component for correctness (#7295)

* Refactor sun component for correctness

* Convert datetimes to dates for astral

* Fix tests for updated code

* Fix times now that calcs are fixed

* Move sun functions to helpers

* Fix flake on new file

* Additional tweaks from review

* Update requirements
This commit is contained in:
Adam Mills 2017-05-09 03:03:34 -04:00 committed by Paulus Schoutsen
parent 419d97fc06
commit 40d27cde0e
19 changed files with 754 additions and 756 deletions

View file

@ -16,8 +16,6 @@ from homeassistant.const import (
from homeassistant.helpers.event import async_track_sunrise, async_track_sunset from homeassistant.helpers.event import async_track_sunrise, async_track_sunset
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
DEPENDENCIES = ['sun']
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
TRIGGER_SCHEMA = vol.Schema({ TRIGGER_SCHEMA = vol.Schema({

View file

@ -14,12 +14,13 @@ from homeassistant.core import callback
import homeassistant.util.dt as dt_util import homeassistant.util.dt as dt_util
from homeassistant.const import STATE_HOME, STATE_NOT_HOME from homeassistant.const import STATE_HOME, STATE_NOT_HOME
from homeassistant.helpers.event import ( 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 from homeassistant.loader import get_component
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
DOMAIN = 'device_sun_light_trigger' DOMAIN = 'device_sun_light_trigger'
DEPENDENCIES = ['light', 'device_tracker', 'group', 'sun'] DEPENDENCIES = ['light', 'device_tracker', 'group']
CONF_DEVICE_GROUP = 'device_group' CONF_DEVICE_GROUP = 'device_group'
CONF_DISABLE_TURN_OFF = 'disable_turn_off' CONF_DISABLE_TURN_OFF = 'disable_turn_off'
@ -50,7 +51,6 @@ def async_setup(hass, config):
device_tracker = get_component('device_tracker') device_tracker = get_component('device_tracker')
group = get_component('group') group = get_component('group')
light = get_component('light') light = get_component('light')
sun = get_component('sun')
conf = config[DOMAIN] conf = config[DOMAIN]
disable_turn_off = conf.get(CONF_DISABLE_TURN_OFF) disable_turn_off = conf.get(CONF_DISABLE_TURN_OFF)
light_group = conf.get(CONF_LIGHT_GROUP, light.ENTITY_ID_ALL_LIGHTS) light_group = conf.get(CONF_LIGHT_GROUP, light.ENTITY_ID_ALL_LIGHTS)
@ -78,7 +78,7 @@ def async_setup(hass, config):
Async friendly. Async friendly.
""" """
next_setting = sun.next_setting(hass) next_setting = get_astral_event_next(hass, 'sunset')
if not next_setting: if not next_setting:
return None return None
return next_setting - LIGHT_TRANSITION_TIME * len(light_ids) 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 # Track every time sun rises so we can schedule a time-based
# pre-sun set event # pre-sun set event
@callback @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. """Turn on all the lights at the moment sun sets.
We will schedule to have each light start after one another We will schedule to have each light start after one another
@ -114,26 +114,26 @@ def async_setup(hass, config):
return return
for index, light_id in enumerate(light_ids): 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), hass, async_turn_on_factory(light_id),
start_point + index * LIGHT_TRANSITION_TIME) start_point + index * LIGHT_TRANSITION_TIME)
async_track_state_change(hass, sun.ENTITY_ID, schedule_light_turn_on, async_track_point_in_utc_time(hass, schedule_light_turn_on,
sun.STATE_BELOW_HORIZON, sun.STATE_ABOVE_HORIZON) get_astral_event_next(hass, 'sunrise'))
# If the sun is already above horizon schedule the time-based pre-sun set # If the sun is already above horizon schedule the time-based pre-sun set
# event. # event.
if sun.is_on(hass): if is_up(hass):
schedule_light_turn_on(None, None, None) schedule_light_turn_on(None)
@callback @callback
def check_light_on_dev_state_change(entity, old_state, new_state): def check_light_on_dev_state_change(entity, old_state, new_state):
"""Handle tracked device state changes.""" """Handle tracked device state changes."""
lights_are_on = group.is_on(hass, light_group) 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 # These variables are needed for the elif check
now = dt_util.now() now = dt_util.utcnow()
start_point = calc_time_for_light_when_sunset() start_point = calc_time_for_light_when_sunset()
# Do we need lights? # 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 # Check this by seeing if current time is later then the point
# in time when we would start putting the lights on. # in time when we would start putting the lights on.
elif (start_point and 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 # Check for every light if it would be on if someone was home
# when the fading in started and turn it on if so # when the fading in started and turn it on if so

View file

@ -15,8 +15,6 @@ import homeassistant.util.dt as dt_util
from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity import Entity
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['astral==1.4']
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
DEFAULT_NAME = 'Moon' DEFAULT_NAME = 'Moon'

View file

@ -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 For more details about this component, please refer to the documentation at
https://home-assistant.io/components/sun/ https://home-assistant.io/components/sun/
""" """
import asyncio
import logging import logging
from datetime import timedelta from datetime import timedelta
import voluptuous as vol
from homeassistant.const import CONF_ELEVATION from homeassistant.const import CONF_ELEVATION
from homeassistant.core import callback
from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity import Entity
from homeassistant.helpers.event import ( 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 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__) _LOGGER = logging.getLogger(__name__)
@ -37,223 +35,16 @@ STATE_ATTR_NEXT_NOON = 'next_noon'
STATE_ATTR_NEXT_RISING = 'next_rising' STATE_ATTR_NEXT_RISING = 'next_rising'
STATE_ATTR_NEXT_SETTING = 'next_setting' STATE_ATTR_NEXT_SETTING = 'next_setting'
CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.Schema({
vol.Optional(CONF_ELEVATION): cv.positive_int,
}),
}, extra=vol.ALLOW_EXTRA)
@asyncio.coroutine
def is_on(hass, entity_id=None): def async_setup(hass, config):
"""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):
"""Track the state of the sun.""" """Track the state of the sun."""
if None in (hass.config.latitude, hass.config.longitude): if config.get(CONF_ELEVATION) is not None:
_LOGGER.error("Latitude or longitude not set in Home Assistant config") _LOGGER.warning(
return False "Elevation is now configured in home assistant core. "
"See https://home-assistant.io/docs/configuration/basic/")
latitude = util.convert(hass.config.latitude, float) sun = Sun(hass, get_astral_location(hass))
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.point_in_time_listener(dt_util.utcnow()) sun.point_in_time_listener(dt_util.utcnow())
return True return True
@ -273,7 +64,7 @@ class Sun(Entity):
self.next_midnight = self.next_noon = None self.next_midnight = self.next_noon = None
self.solar_elevation = self.solar_azimuth = 0 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 @property
def name(self): def name(self):
@ -308,64 +99,41 @@ class Sun(Entity):
return min(self.next_dawn, self.next_dusk, self.next_midnight, return min(self.next_dawn, self.next_dusk, self.next_midnight,
self.next_noon, self.next_rising, self.next_setting) self.next_noon, self.next_rising, self.next_setting)
@staticmethod @callback
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
def update_as_of(self, utc_point_in_time): def update_as_of(self, utc_point_in_time):
"""Update the attributes containing solar events.""" """Update the attributes containing solar events."""
self.next_dawn = Sun.get_next_solar_event( self.next_dawn = get_astral_event_next(
self.location.dawn, utc_point_in_time, -1, 1) self.hass, 'dawn', utc_point_in_time)
self.next_dusk = Sun.get_next_solar_event( self.next_dusk = get_astral_event_next(
self.location.dusk, utc_point_in_time, -1, 1) self.hass, 'dusk', utc_point_in_time)
self.next_midnight = Sun.get_next_solar_event( self.next_midnight = get_astral_event_next(
self.location.solar_midnight, utc_point_in_time, -1, 1) self.hass, 'solar_midnight', utc_point_in_time)
self.next_noon = Sun.get_next_solar_event( self.next_noon = get_astral_event_next(
self.location.solar_noon, utc_point_in_time, -1, 1) self.hass, 'solar_noon', utc_point_in_time)
self.next_rising = Sun.get_next_solar_event( self.next_rising = get_astral_event_next(
self.location.sunrise, utc_point_in_time, -1, 1) self.hass, 'sunrise', utc_point_in_time)
self.next_setting = Sun.get_next_solar_event( self.next_setting = get_astral_event_next(
self.location.sunset, utc_point_in_time, -1, 1) self.hass, 'sunset', utc_point_in_time)
@callback
def update_sun_position(self, utc_point_in_time): def update_sun_position(self, utc_point_in_time):
"""Calculate the position of the sun.""" """Calculate the position of the sun."""
from astral import Astral self.solar_azimuth = self.location.solar_azimuth(utc_point_in_time)
self.solar_elevation = self.location.solar_elevation(utc_point_in_time)
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)
@callback
def point_in_time_listener(self, now): def point_in_time_listener(self, now):
"""Run when the state of the sun has changed.""" """Run when the state of the sun has changed."""
self.update_as_of(now) 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 # 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.hass, self.point_in_time_listener,
self.next_change + timedelta(seconds=1)) self.next_change + timedelta(seconds=1))
@callback
def timer_update(self, time): def timer_update(self, time):
"""Needed to update solar elevation and azimuth.""" """Needed to update solar elevation and azimuth."""
self.update_sun_position(time) self.update_sun_position(time)
self.schedule_update_ha_state() self.hass.async_add_job(self.async_update_ha_state())

View file

@ -11,18 +11,18 @@ import logging
import voluptuous as vol import voluptuous as vol
from homeassistant.components.light import is_on, turn_on 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.components.switch import DOMAIN, SwitchDevice
from homeassistant.const import CONF_NAME, CONF_PLATFORM from homeassistant.const import CONF_NAME, CONF_PLATFORM
from homeassistant.helpers.event import track_time_change from homeassistant.helpers.event import track_time_change
from homeassistant.helpers.sun import get_astral_event_date
from homeassistant.util.color import ( from homeassistant.util.color import (
color_temperature_to_rgb, color_RGB_to_xy, color_temperature_to_rgb, color_RGB_to_xy,
color_temperature_kelvin_to_mired) color_temperature_kelvin_to_mired)
from homeassistant.util.dt import now as dt_now from homeassistant.util.dt import now as dt_now
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
DEPENDENCIES = ['sun', 'light'] DEPENDENCIES = ['light']
SUN = "sun.sun"
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
CONF_LIGHTS = 'lights' CONF_LIGHTS = 'lights'
@ -159,8 +159,7 @@ class FluxSwitch(SwitchDevice):
"""Update all the lights using flux.""" """Update all the lights using flux."""
if now is None: if now is None:
now = dt_now() now = dt_now()
sunset = next_setting(self.hass, SUN).replace( sunset = get_astral_event_date(self.hass, 'sunset', now.date())
day=now.day, month=now.month, year=now.year)
start_time = self.find_start_time(now) start_time = self.find_start_time(now)
stop_time = now.replace( stop_time = now.replace(
hour=self._stop_time.hour, minute=self._stop_time.minute, 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, hour=self._start_time.hour, minute=self._start_time.minute,
second=0) second=0)
else: else:
sunrise = next_rising(self.hass, SUN).replace( sunrise = get_astral_event_date(self.hass, 'sunrise', now.date())
day=now.day, month=now.month, year=now.year)
return sunrise return sunrise

View file

@ -7,8 +7,7 @@ import sys
from homeassistant.helpers.typing import ConfigType from homeassistant.helpers.typing import ConfigType
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.components import ( from homeassistant.components import zone as zone_cmp
zone as zone_cmp, sun as sun_cmp)
from homeassistant.const import ( from homeassistant.const import (
ATTR_GPS_ACCURACY, ATTR_LATITUDE, ATTR_LONGITUDE, ATTR_GPS_ACCURACY, ATTR_LATITUDE, ATTR_LONGITUDE,
CONF_ENTITY_ID, CONF_VALUE_TEMPLATE, CONF_CONDITION, CONF_ENTITY_ID, CONF_VALUE_TEMPLATE, CONF_CONDITION,
@ -17,6 +16,7 @@ from homeassistant.const import (
CONF_BELOW, CONF_ABOVE) CONF_BELOW, CONF_ABOVE)
from homeassistant.exceptions import TemplateError, HomeAssistantError from homeassistant.exceptions import TemplateError, HomeAssistantError
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.sun import get_astral_event_date
import homeassistant.util.dt as dt_util import homeassistant.util.dt as dt_util
from homeassistant.util.async import run_callback_threadsafe 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): def sun(hass, before=None, after=None, before_offset=None, after_offset=None):
"""Test if current time matches sun requirements.""" """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) before_offset = before_offset or timedelta(0)
after_offset = after_offset or timedelta(0) after_offset = after_offset or timedelta(0)
if before == SUN_EVENT_SUNRISE and now > (sun_cmp.next_rising(hass) + sunrise = get_astral_event_date(hass, 'sunrise', today)
before_offset).time(): 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 return False
elif before == SUN_EVENT_SUNSET and now > (sun_cmp.next_setting(hass) + if sunset is None and (before == SUN_EVENT_SUNSET or
before_offset).time(): after == SUN_EVENT_SUNSET):
# There is no sunset today
return False return False
if after == SUN_EVENT_SUNRISE and now < (sun_cmp.next_rising(hass) + if before == SUN_EVENT_SUNRISE and utcnow > sunrise + before_offset:
after_offset).time():
return False return False
elif after == SUN_EVENT_SUNSET and now < (sun_cmp.next_setting(hass) + elif before == SUN_EVENT_SUNSET and utcnow > sunset + before_offset:
after_offset).time(): 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 False
return True return True

View file

@ -1,7 +1,7 @@
"""Helpers for listening to events.""" """Helpers for listening to events."""
import functools as ft import functools as ft
from datetime import timedelta
from homeassistant.helpers.sun import get_astral_event_next
from ..core import HomeAssistant, callback from ..core import HomeAssistant, callback
from ..const import ( from ..const import (
ATTR_NOW, EVENT_STATE_CHANGED, EVENT_TIME_CHANGED, MATCH_ALL) 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 @callback
def async_track_sunrise(hass, action, offset=None): def async_track_sunrise(hass, action, offset=None):
"""Add a listener that will fire a specified offset from sunrise daily.""" """Add a listener that will fire a specified offset from sunrise daily."""
from homeassistant.components import sun
offset = offset or timedelta()
remove = None 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 @callback
def sunrise_automation_listener(now): def sunrise_automation_listener(now):
"""Handle points in time to execute actions.""" """Handle points in time to execute actions."""
nonlocal remove nonlocal remove
remove = async_track_point_in_utc_time( 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) hass.async_run_job(action)
remove = async_track_point_in_utc_time( 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(): def remove_listener():
"""Remove sunset listener.""" """Remove sunset listener."""
@ -234,29 +225,20 @@ track_sunrise = threaded_listener_factory(async_track_sunrise)
@callback @callback
def async_track_sunset(hass, action, offset=None): def async_track_sunset(hass, action, offset=None):
"""Add a listener that will fire a specified offset from sunset daily.""" """Add a listener that will fire a specified offset from sunset daily."""
from homeassistant.components import sun
offset = offset or timedelta()
remove = None 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 @callback
def sunset_automation_listener(now): def sunset_automation_listener(now):
"""Handle points in time to execute actions.""" """Handle points in time to execute actions."""
nonlocal remove nonlocal remove
remove = async_track_point_in_utc_time( 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) hass.async_run_job(action)
remove = async_track_point_in_utc_time( 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(): def remove_listener():
"""Remove sunset listener.""" """Remove sunset listener."""

View file

@ -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

View file

@ -8,3 +8,4 @@ typing>=3,<4
aiohttp==2.0.7 aiohttp==2.0.7
async_timeout==1.2.1 async_timeout==1.2.1
chardet==3.0.2 chardet==3.0.2
astral==1.4

View file

@ -9,6 +9,7 @@ typing>=3,<4
aiohttp==2.0.7 aiohttp==2.0.7
async_timeout==1.2.1 async_timeout==1.2.1
chardet==3.0.2 chardet==3.0.2
astral==1.4
# homeassistant.components.nuimo_controller # homeassistant.components.nuimo_controller
--only-binary=all https://github.com/getSenic/nuimo-linux-python/archive/29fc42987f74d8090d0e2382e8f248ff5990b8c9.zip#nuimo==1.0.0 --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 # homeassistant.components.notify.apns
apns2==0.1.1 apns2==0.1.1
# homeassistant.components.sun
# homeassistant.components.sensor.moon
astral==1.4
# homeassistant.components.light.avion # homeassistant.components.light.avion
# avion==0.6 # avion==0.6

View file

@ -37,10 +37,6 @@ aiohttp_cors==0.5.3
# homeassistant.components.notify.apns # homeassistant.components.notify.apns
apns2==0.1.1 apns2==0.1.1
# homeassistant.components.sun
# homeassistant.components.sensor.moon
astral==1.4
# homeassistant.components.datadog # homeassistant.components.datadog
datadog==0.15.0 datadog==0.15.0

View file

@ -24,7 +24,8 @@ REQUIRES = [
'typing>=3,<4', 'typing>=3,<4',
'aiohttp==2.0.7', 'aiohttp==2.0.7',
'async_timeout==1.2.1', 'async_timeout==1.2.1',
'chardet==3.0.2' 'chardet==3.0.2',
'astral==1.4',
] ]
setup( setup(

View file

@ -3,7 +3,6 @@ import asyncio
import functools as ft import functools as ft
import os import os
import sys import sys
from datetime import timedelta
from unittest.mock import patch, MagicMock, Mock from unittest.mock import patch, MagicMock, Mock
from io import StringIO from io import StringIO
import logging import logging
@ -25,7 +24,7 @@ from homeassistant.const import (
STATE_ON, STATE_OFF, DEVICE_DEFAULT_NAME, EVENT_TIME_CHANGED, STATE_ON, STATE_OFF, DEVICE_DEFAULT_NAME, EVENT_TIME_CHANGED,
EVENT_STATE_CHANGED, EVENT_PLATFORM_DISCOVERED, ATTR_SERVICE, EVENT_STATE_CHANGED, EVENT_PLATFORM_DISCOVERED, ATTR_SERVICE,
ATTR_DISCOVERED, SERVER_PORT, EVENT_HOMEASSISTANT_CLOSE) 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.auth import auth_middleware
from homeassistant.components.http.const import ( from homeassistant.components.http.const import (
KEY_USE_X_FORWARDED_FOR, KEY_BANS_ENABLED, KEY_TRUSTED_NETWORKS) 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): def load_fixture(filename):
"""Load a fixture.""" """Load a fixture."""
path = os.path.join(os.path.dirname(__file__), 'fixtures', filename) path = os.path.join(os.path.dirname(__file__), 'fixtures', filename)

View file

@ -22,7 +22,8 @@ class TestAutomationSun(unittest.TestCase):
"""Setup things to be run when tests are started.""" """Setup things to be run when tests are started."""
self.hass = get_test_home_assistant() self.hass = get_test_home_assistant()
mock_component(self.hass, 'group') mock_component(self.hass, 'group')
mock_component(self.hass, 'sun') setup_component(self.hass, sun.DOMAIN, {
sun.DOMAIN: {sun.CONF_ELEVATION: 0}})
self.calls = [] self.calls = []
@ -39,10 +40,6 @@ class TestAutomationSun(unittest.TestCase):
def test_sunset_trigger(self): def test_sunset_trigger(self):
"""Test the sunset trigger.""" """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) now = datetime(2015, 9, 15, 23, tzinfo=dt_util.UTC)
trigger_time = datetime(2015, 9, 16, 2, 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): def test_sunrise_trigger(self):
"""Test the sunrise trigger.""" """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) now = datetime(2015, 9, 13, 23, tzinfo=dt_util.UTC)
trigger_time = datetime(2015, 9, 16, 14, 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): def test_sunset_trigger_with_offset(self):
"""Test the sunset trigger with offset.""" """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) now = datetime(2015, 9, 15, 23, tzinfo=dt_util.UTC)
trigger_time = datetime(2015, 9, 16, 2, 30, 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): def test_sunrise_trigger_with_offset(self):
"""Test the runrise trigger with offset.""" """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) now = datetime(2015, 9, 13, 23, tzinfo=dt_util.UTC)
trigger_time = datetime(2015, 9, 16, 13, 30, 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): def test_if_action_before(self):
"""Test if action was before.""" """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, { setup_component(self.hass, automation.DOMAIN, {
automation.DOMAIN: { automation.DOMAIN: {
'trigger': { 'trigger': {
@ -188,14 +169,14 @@ class TestAutomationSun(unittest.TestCase):
}) })
now = datetime(2015, 9, 16, 15, tzinfo=dt_util.UTC) 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): return_value=now):
self.hass.bus.fire('test_event') self.hass.bus.fire('test_event')
self.hass.block_till_done() self.hass.block_till_done()
self.assertEqual(0, len(self.calls)) self.assertEqual(0, len(self.calls))
now = datetime(2015, 9, 16, 10, tzinfo=dt_util.UTC) 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): return_value=now):
self.hass.bus.fire('test_event') self.hass.bus.fire('test_event')
self.hass.block_till_done() self.hass.block_till_done()
@ -203,10 +184,6 @@ class TestAutomationSun(unittest.TestCase):
def test_if_action_after(self): def test_if_action_after(self):
"""Test if action was after.""" """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, { setup_component(self.hass, automation.DOMAIN, {
automation.DOMAIN: { automation.DOMAIN: {
'trigger': { 'trigger': {
@ -224,14 +201,14 @@ class TestAutomationSun(unittest.TestCase):
}) })
now = datetime(2015, 9, 16, 13, tzinfo=dt_util.UTC) 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): return_value=now):
self.hass.bus.fire('test_event') self.hass.bus.fire('test_event')
self.hass.block_till_done() self.hass.block_till_done()
self.assertEqual(0, len(self.calls)) self.assertEqual(0, len(self.calls))
now = datetime(2015, 9, 16, 15, tzinfo=dt_util.UTC) 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): return_value=now):
self.hass.bus.fire('test_event') self.hass.bus.fire('test_event')
self.hass.block_till_done() self.hass.block_till_done()
@ -239,10 +216,6 @@ class TestAutomationSun(unittest.TestCase):
def test_if_action_before_with_offset(self): def test_if_action_before_with_offset(self):
"""Test if action was before offset.""" """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, { setup_component(self.hass, automation.DOMAIN, {
automation.DOMAIN: { automation.DOMAIN: {
'trigger': { 'trigger': {
@ -260,15 +233,15 @@ class TestAutomationSun(unittest.TestCase):
} }
}) })
now = datetime(2015, 9, 16, 15, 1, tzinfo=dt_util.UTC) now = datetime(2015, 9, 16, 14, 32, 44, tzinfo=dt_util.UTC)
with patch('homeassistant.util.dt.now', with patch('homeassistant.util.dt.utcnow',
return_value=now): return_value=now):
self.hass.bus.fire('test_event') self.hass.bus.fire('test_event')
self.hass.block_till_done() self.hass.block_till_done()
self.assertEqual(0, len(self.calls)) self.assertEqual(0, len(self.calls))
now = datetime(2015, 9, 16, 15, tzinfo=dt_util.UTC) now = datetime(2015, 9, 16, 14, 32, 43, tzinfo=dt_util.UTC)
with patch('homeassistant.util.dt.now', with patch('homeassistant.util.dt.utcnow',
return_value=now): return_value=now):
self.hass.bus.fire('test_event') self.hass.bus.fire('test_event')
self.hass.block_till_done() self.hass.block_till_done()
@ -276,10 +249,6 @@ class TestAutomationSun(unittest.TestCase):
def test_if_action_after_with_offset(self): def test_if_action_after_with_offset(self):
"""Test if action was after offset.""" """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, { setup_component(self.hass, automation.DOMAIN, {
automation.DOMAIN: { automation.DOMAIN: {
'trigger': { 'trigger': {
@ -297,15 +266,15 @@ class TestAutomationSun(unittest.TestCase):
} }
}) })
now = datetime(2015, 9, 16, 14, 59, tzinfo=dt_util.UTC) now = datetime(2015, 9, 16, 14, 32, 42, tzinfo=dt_util.UTC)
with patch('homeassistant.util.dt.now', with patch('homeassistant.util.dt.utcnow',
return_value=now): return_value=now):
self.hass.bus.fire('test_event') self.hass.bus.fire('test_event')
self.hass.block_till_done() self.hass.block_till_done()
self.assertEqual(0, len(self.calls)) self.assertEqual(0, len(self.calls))
now = datetime(2015, 9, 16, 15, tzinfo=dt_util.UTC) now = datetime(2015, 9, 16, 14, 32, 43, tzinfo=dt_util.UTC)
with patch('homeassistant.util.dt.now', with patch('homeassistant.util.dt.utcnow',
return_value=now): return_value=now):
self.hass.bus.fire('test_event') self.hass.bus.fire('test_event')
self.hass.block_till_done() self.hass.block_till_done()
@ -313,11 +282,6 @@ class TestAutomationSun(unittest.TestCase):
def test_if_action_before_and_after_during(self): def test_if_action_before_and_after_during(self):
"""Test if action was before and after during.""" """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, { setup_component(self.hass, automation.DOMAIN, {
automation.DOMAIN: { automation.DOMAIN: {
'trigger': { 'trigger': {
@ -335,62 +299,22 @@ class TestAutomationSun(unittest.TestCase):
} }
}) })
now = datetime(2015, 9, 16, 9, 59, tzinfo=dt_util.UTC) now = datetime(2015, 9, 16, 13, 8, 51, tzinfo=dt_util.UTC)
with patch('homeassistant.util.dt.now', with patch('homeassistant.util.dt.utcnow',
return_value=now): return_value=now):
self.hass.bus.fire('test_event') self.hass.bus.fire('test_event')
self.hass.block_till_done() self.hass.block_till_done()
self.assertEqual(0, len(self.calls)) self.assertEqual(0, len(self.calls))
now = datetime(2015, 9, 16, 15, 1, tzinfo=dt_util.UTC) now = datetime(2015, 9, 17, 2, 25, 18, tzinfo=dt_util.UTC)
with patch('homeassistant.util.dt.now', with patch('homeassistant.util.dt.utcnow',
return_value=now): return_value=now):
self.hass.bus.fire('test_event') self.hass.bus.fire('test_event')
self.hass.block_till_done() self.hass.block_till_done()
self.assertEqual(0, len(self.calls)) self.assertEqual(0, len(self.calls))
now = datetime(2015, 9, 16, 12, tzinfo=dt_util.UTC) now = datetime(2015, 9, 16, 16, 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(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',
return_value=now): return_value=now):
self.hass.bus.fire('test_event') self.hass.bus.fire('test_event')
self.hass.block_till_done() self.hass.block_till_done()

View file

@ -1,5 +1,4 @@
"""The tests for the Flux switch platform.""" """The tests for the Flux switch platform."""
from datetime import timedelta
import unittest import unittest
from unittest.mock import patch from unittest.mock import patch
@ -86,17 +85,19 @@ class TestSwitchFlux(unittest.TestCase):
self.assertIsNone(state.attributes.get('xy_color')) self.assertIsNone(state.attributes.get('xy_color'))
self.assertIsNone(state.attributes.get('brightness')) self.assertIsNone(state.attributes.get('brightness'))
test_time = dt_util.now().replace(hour=10, minute=30, test_time = dt_util.now().replace(hour=10, minute=30, second=0)
second=0) sunset_time = test_time.replace(hour=17, minute=0, second=0)
sunset_time = test_time.replace(hour=17, minute=0, sunrise_time = test_time.replace(hour=5, minute=0, second=0)
second=0)
sunrise_time = test_time.replace(hour=5, minute=0, def event_date(hass, event, now=None):
second=0) + timedelta(days=1) if event == 'sunrise':
return sunrise_time
else:
return sunset_time
with patch('homeassistant.util.dt.now', return_value=test_time): with patch('homeassistant.util.dt.now', return_value=test_time):
with patch('homeassistant.components.sun.next_rising', with patch('homeassistant.helpers.sun.get_astral_event_date',
return_value=sunrise_time): side_effect=event_date):
with patch('homeassistant.components.sun.next_setting',
return_value=sunset_time):
assert setup_component(self.hass, switch.DOMAIN, { assert setup_component(self.hass, switch.DOMAIN, {
switch.DOMAIN: { switch.DOMAIN: {
'platform': 'flux', 'platform': 'flux',
@ -126,17 +127,19 @@ class TestSwitchFlux(unittest.TestCase):
self.assertIsNone(state.attributes.get('xy_color')) self.assertIsNone(state.attributes.get('xy_color'))
self.assertIsNone(state.attributes.get('brightness')) self.assertIsNone(state.attributes.get('brightness'))
test_time = dt_util.now().replace(hour=2, minute=30, test_time = dt_util.now().replace(hour=2, minute=30, second=0)
second=0) sunset_time = test_time.replace(hour=17, minute=0, second=0)
sunset_time = test_time.replace(hour=17, minute=0, sunrise_time = test_time.replace(hour=5, minute=0, second=0)
second=0)
sunrise_time = test_time.replace(hour=5, minute=0, def event_date(hass, event, now=None):
second=0) + timedelta(days=1) if event == 'sunrise':
return sunrise_time
else:
return sunset_time
with patch('homeassistant.util.dt.now', return_value=test_time): with patch('homeassistant.util.dt.now', return_value=test_time):
with patch('homeassistant.components.sun.next_rising', with patch('homeassistant.helpers.sun.get_astral_event_date',
return_value=sunrise_time): side_effect=event_date):
with patch('homeassistant.components.sun.next_setting',
return_value=sunset_time):
assert setup_component(self.hass, switch.DOMAIN, { assert setup_component(self.hass, switch.DOMAIN, {
switch.DOMAIN: { switch.DOMAIN: {
'platform': 'flux', 'platform': 'flux',
@ -173,15 +176,17 @@ class TestSwitchFlux(unittest.TestCase):
test_time = dt_util.now().replace(hour=8, minute=30, second=0) test_time = dt_util.now().replace(hour=8, minute=30, second=0)
sunset_time = test_time.replace(hour=17, minute=0, second=0) sunset_time = test_time.replace(hour=17, minute=0, second=0)
sunrise_time = test_time.replace(hour=5, sunrise_time = test_time.replace(hour=5, minute=0, second=0)
minute=0,
second=0) + timedelta(days=1) 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.util.dt.now', return_value=test_time):
with patch('homeassistant.components.sun.next_rising', with patch('homeassistant.helpers.sun.get_astral_event_date',
return_value=sunrise_time): side_effect=event_date):
with patch('homeassistant.components.sun.next_setting',
return_value=sunset_time):
assert setup_component(self.hass, switch.DOMAIN, { assert setup_component(self.hass, switch.DOMAIN, {
switch.DOMAIN: { switch.DOMAIN: {
'platform': 'flux', 'platform': 'flux',
@ -218,15 +223,17 @@ class TestSwitchFlux(unittest.TestCase):
test_time = dt_util.now().replace(hour=17, minute=30, second=0) test_time = dt_util.now().replace(hour=17, minute=30, second=0)
sunset_time = test_time.replace(hour=17, minute=0, second=0) sunset_time = test_time.replace(hour=17, minute=0, second=0)
sunrise_time = test_time.replace(hour=5, sunrise_time = test_time.replace(hour=5, minute=0, second=0)
minute=0,
second=0) + timedelta(days=1) 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.util.dt.now', return_value=test_time):
with patch('homeassistant.components.sun.next_rising', with patch('homeassistant.helpers.sun.get_astral_event_date',
return_value=sunrise_time): side_effect=event_date):
with patch('homeassistant.components.sun.next_setting',
return_value=sunset_time):
assert setup_component(self.hass, switch.DOMAIN, { assert setup_component(self.hass, switch.DOMAIN, {
switch.DOMAIN: { switch.DOMAIN: {
'platform': 'flux', 'platform': 'flux',
@ -263,15 +270,17 @@ class TestSwitchFlux(unittest.TestCase):
test_time = dt_util.now().replace(hour=23, minute=30, second=0) test_time = dt_util.now().replace(hour=23, minute=30, second=0)
sunset_time = test_time.replace(hour=17, minute=0, second=0) sunset_time = test_time.replace(hour=17, minute=0, second=0)
sunrise_time = test_time.replace(hour=5, sunrise_time = test_time.replace(hour=5, minute=0, second=0)
minute=0,
second=0) + timedelta(days=1) 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.util.dt.now', return_value=test_time):
with patch('homeassistant.components.sun.next_rising', with patch('homeassistant.helpers.sun.get_astral_event_date',
return_value=sunrise_time): side_effect=event_date):
with patch('homeassistant.components.sun.next_setting',
return_value=sunset_time):
assert setup_component(self.hass, switch.DOMAIN, { assert setup_component(self.hass, switch.DOMAIN, {
switch.DOMAIN: { switch.DOMAIN: {
'platform': 'flux', 'platform': 'flux',
@ -308,15 +317,17 @@ class TestSwitchFlux(unittest.TestCase):
test_time = dt_util.now().replace(hour=17, minute=30, second=0) test_time = dt_util.now().replace(hour=17, minute=30, second=0)
sunset_time = test_time.replace(hour=17, minute=0, second=0) sunset_time = test_time.replace(hour=17, minute=0, second=0)
sunrise_time = test_time.replace(hour=5, sunrise_time = test_time.replace(hour=5, minute=0, second=0)
minute=0,
second=0) + timedelta(days=1) 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.util.dt.now', return_value=test_time):
with patch('homeassistant.components.sun.next_rising', with patch('homeassistant.helpers.sun.get_astral_event_date',
return_value=sunrise_time): side_effect=event_date):
with patch('homeassistant.components.sun.next_setting',
return_value=sunset_time):
assert setup_component(self.hass, switch.DOMAIN, { assert setup_component(self.hass, switch.DOMAIN, {
switch.DOMAIN: { switch.DOMAIN: {
'platform': 'flux', 'platform': 'flux',
@ -355,15 +366,17 @@ class TestSwitchFlux(unittest.TestCase):
test_time = dt_util.now().replace(hour=17, minute=30, second=0) test_time = dt_util.now().replace(hour=17, minute=30, second=0)
sunset_time = test_time.replace(hour=17, minute=0, second=0) sunset_time = test_time.replace(hour=17, minute=0, second=0)
sunrise_time = test_time.replace(hour=5, sunrise_time = test_time.replace(hour=5, minute=0, second=0)
minute=0,
second=0) + timedelta(days=1) 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.util.dt.now', return_value=test_time):
with patch('homeassistant.components.sun.next_rising', with patch('homeassistant.helpers.sun.get_astral_event_date',
return_value=sunrise_time): side_effect=event_date):
with patch('homeassistant.components.sun.next_setting',
return_value=sunset_time):
assert setup_component(self.hass, switch.DOMAIN, { assert setup_component(self.hass, switch.DOMAIN, {
switch.DOMAIN: { switch.DOMAIN: {
'platform': 'flux', 'platform': 'flux',
@ -402,15 +415,17 @@ class TestSwitchFlux(unittest.TestCase):
test_time = dt_util.now().replace(hour=17, minute=30, second=0) test_time = dt_util.now().replace(hour=17, minute=30, second=0)
sunset_time = test_time.replace(hour=17, minute=0, second=0) sunset_time = test_time.replace(hour=17, minute=0, second=0)
sunrise_time = test_time.replace(hour=5, sunrise_time = test_time.replace(hour=5, minute=0, second=0)
minute=0,
second=0) + timedelta(days=1) 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.util.dt.now', return_value=test_time):
with patch('homeassistant.components.sun.next_rising', with patch('homeassistant.helpers.sun.get_astral_event_date',
return_value=sunrise_time): side_effect=event_date):
with patch('homeassistant.components.sun.next_setting',
return_value=sunset_time):
assert setup_component(self.hass, switch.DOMAIN, { assert setup_component(self.hass, switch.DOMAIN, {
switch.DOMAIN: { switch.DOMAIN: {
'platform': 'flux', 'platform': 'flux',
@ -460,15 +475,19 @@ class TestSwitchFlux(unittest.TestCase):
test_time = dt_util.now().replace(hour=12, minute=0, second=0) test_time = dt_util.now().replace(hour=12, minute=0, second=0)
sunset_time = test_time.replace(hour=17, minute=0, second=0) sunset_time = test_time.replace(hour=17, minute=0, second=0)
sunrise_time = test_time.replace(hour=5, sunrise_time = test_time.replace(hour=5, minute=0, second=0)
minute=0,
second=0) + timedelta(days=1) 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.util.dt.now', return_value=test_time):
with patch('homeassistant.components.sun.next_rising', with patch('homeassistant.helpers.sun.get_astral_event_date',
return_value=sunrise_time): side_effect=event_date):
with patch('homeassistant.components.sun.next_setting',
return_value=sunset_time):
assert setup_component(self.hass, switch.DOMAIN, { assert setup_component(self.hass, switch.DOMAIN, {
switch.DOMAIN: { switch.DOMAIN: {
'platform': 'flux', 'platform': 'flux',
@ -511,15 +530,17 @@ class TestSwitchFlux(unittest.TestCase):
test_time = dt_util.now().replace(hour=8, minute=30, second=0) test_time = dt_util.now().replace(hour=8, minute=30, second=0)
sunset_time = test_time.replace(hour=17, minute=0, second=0) sunset_time = test_time.replace(hour=17, minute=0, second=0)
sunrise_time = test_time.replace(hour=5, sunrise_time = test_time.replace(hour=5, minute=0, second=0)
minute=0,
second=0) + timedelta(days=1) 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.util.dt.now', return_value=test_time):
with patch('homeassistant.components.sun.next_rising', with patch('homeassistant.helpers.sun.get_astral_event_date',
return_value=sunrise_time): side_effect=event_date):
with patch('homeassistant.components.sun.next_setting',
return_value=sunset_time):
assert setup_component(self.hass, switch.DOMAIN, { assert setup_component(self.hass, switch.DOMAIN, {
switch.DOMAIN: { switch.DOMAIN: {
'platform': 'flux', 'platform': 'flux',

View file

@ -1,17 +1,19 @@
"""The tests device sun light trigger component.""" """The tests device sun light trigger component."""
# pylint: disable=protected-access # pylint: disable=protected-access
from datetime import datetime
import os import os
import unittest import unittest
from unittest.mock import patch
from homeassistant.setup import setup_component from homeassistant.setup import setup_component
import homeassistant.loader as loader import homeassistant.loader as loader
from homeassistant.const import CONF_PLATFORM, STATE_HOME, STATE_NOT_HOME from homeassistant.const import CONF_PLATFORM, STATE_HOME, STATE_NOT_HOME
from homeassistant.components import ( 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 ( from tests.common import (
get_test_config_dir, get_test_home_assistant, ensure_sun_risen, get_test_config_dir, get_test_home_assistant, fire_time_changed)
ensure_sun_set)
KNOWN_DEV_YAML_PATH = os.path.join(get_test_config_dir(), KNOWN_DEV_YAML_PATH = os.path.join(get_test_config_dir(),
@ -61,25 +63,25 @@ class TestDeviceSunLightTrigger(unittest.TestCase):
light.DOMAIN: {CONF_PLATFORM: 'test'} 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 def tearDown(self): # pylint: disable=invalid-name
"""Stop everything that was started.""" """Stop everything that was started."""
self.hass.stop() self.hass.stop()
def test_lights_on_when_sun_sets(self): def test_lights_on_when_sun_sets(self):
"""Test lights go on when there is someone home and the sun sets.""" """Test lights go on when there is someone home and the sun sets."""
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.assertTrue(setup_component(
self.hass, device_sun_light_trigger.DOMAIN, { self.hass, device_sun_light_trigger.DOMAIN, {
device_sun_light_trigger.DOMAIN: {}})) device_sun_light_trigger.DOMAIN: {}}))
ensure_sun_risen(self.hass)
light.turn_off(self.hass) light.turn_off(self.hass)
self.hass.block_till_done() self.hass.block_till_done()
ensure_sun_set(self.hass) 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.hass.block_till_done()
self.assertTrue(light.is_on(self.hass)) self.assertTrue(light.is_on(self.hass))
@ -105,9 +107,9 @@ class TestDeviceSunLightTrigger(unittest.TestCase):
def test_lights_turn_on_when_coming_home_after_sun_set(self): \ def test_lights_turn_on_when_coming_home_after_sun_set(self): \
# pylint: disable=invalid-name # pylint: disable=invalid-name
"""Test lights turn on when coming home after sun set.""" """Test lights turn on when coming home after sun set."""
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) light.turn_off(self.hass)
ensure_sun_set(self.hass)
self.hass.block_till_done() self.hass.block_till_done()
self.assertTrue(setup_component( self.assertTrue(setup_component(

View file

@ -24,118 +24,111 @@ class TestSun(unittest.TestCase):
"""Stop everything that was started.""" """Stop everything that was started."""
self.hass.stop() 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): def test_setting_rising(self):
"""Test retrieving sun setting and rising.""" """Test retrieving sun setting and rising."""
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, { setup_component(self.hass, sun.DOMAIN, {
sun.DOMAIN: {sun.CONF_ELEVATION: 0}}) sun.DOMAIN: {sun.CONF_ELEVATION: 0}})
self.hass.block_till_done()
state = self.hass.states.get(sun.ENTITY_ID)
from astral import Astral from astral import Astral
astral = Astral() astral = Astral()
utc_now = dt_util.utcnow() utc_today = utc_now.date()
latitude = self.hass.config.latitude latitude = self.hass.config.latitude
longitude = self.hass.config.longitude longitude = self.hass.config.longitude
mod = -1 mod = -1
while True: while True:
next_dawn = (astral.dawn_utc(utc_now + next_dawn = (astral.dawn_utc(
timedelta(days=mod), latitude, longitude)) utc_today + timedelta(days=mod), latitude, longitude))
if next_dawn > utc_now: if next_dawn > utc_now:
break break
mod += 1 mod += 1
mod = -1 mod = -1
while True: while True:
next_dusk = (astral.dusk_utc(utc_now + next_dusk = (astral.dusk_utc(
timedelta(days=mod), latitude, longitude)) utc_today + timedelta(days=mod), latitude, longitude))
if next_dusk > utc_now: if next_dusk > utc_now:
break break
mod += 1 mod += 1
mod = -1 mod = -1
while True: while True:
next_midnight = (astral.solar_midnight_utc(utc_now + next_midnight = (astral.solar_midnight_utc(
timedelta(days=mod), longitude)) utc_today + timedelta(days=mod), longitude))
if next_midnight > utc_now: if next_midnight > utc_now:
break break
mod += 1 mod += 1
mod = -1 mod = -1
while True: while True:
next_noon = (astral.solar_noon_utc(utc_now + next_noon = (astral.solar_noon_utc(
timedelta(days=mod), longitude)) utc_today + timedelta(days=mod), longitude))
if next_noon > utc_now: if next_noon > utc_now:
break break
mod += 1 mod += 1
mod = -1 mod = -1
while True: while True:
next_rising = (astral.sunrise_utc(utc_now + next_rising = (astral.sunrise_utc(
timedelta(days=mod), latitude, longitude)) utc_today + timedelta(days=mod), latitude, longitude))
if next_rising > utc_now: if next_rising > utc_now:
break break
mod += 1 mod += 1
mod = -1 mod = -1
while True: while True:
next_setting = (astral.sunset_utc(utc_now + next_setting = (astral.sunset_utc(
timedelta(days=mod), latitude, longitude)) utc_today + timedelta(days=mod), latitude, longitude))
if next_setting > utc_now: if next_setting > utc_now:
break break
mod += 1 mod += 1
self.assertEqual(next_dawn, sun.next_dawn_utc(self.hass)) self.assertEqual(next_dawn, dt_util.parse_datetime(
self.assertEqual(next_dusk, sun.next_dusk_utc(self.hass)) state.attributes[sun.STATE_ATTR_NEXT_DAWN]))
self.assertEqual(next_midnight, sun.next_midnight_utc(self.hass)) self.assertEqual(next_dusk, dt_util.parse_datetime(
self.assertEqual(next_noon, sun.next_noon_utc(self.hass)) state.attributes[sun.STATE_ATTR_NEXT_DUSK]))
self.assertEqual(next_rising, sun.next_rising_utc(self.hass)) self.assertEqual(next_midnight, dt_util.parse_datetime(
self.assertEqual(next_setting, sun.next_setting_utc(self.hass)) state.attributes[sun.STATE_ATTR_NEXT_MIDNIGHT]))
self.assertEqual(next_noon, dt_util.parse_datetime(
# Point it at a state without the proper attributes state.attributes[sun.STATE_ATTR_NEXT_NOON]))
self.hass.states.set(sun.ENTITY_ID, sun.STATE_ABOVE_HORIZON) self.assertEqual(next_rising, dt_util.parse_datetime(
self.assertIsNone(sun.next_dawn(self.hass)) state.attributes[sun.STATE_ATTR_NEXT_RISING]))
self.assertIsNone(sun.next_dusk(self.hass)) self.assertEqual(next_setting, dt_util.parse_datetime(
self.assertIsNone(sun.next_midnight(self.hass)) state.attributes[sun.STATE_ATTR_NEXT_SETTING]))
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'))
def test_state_change(self): def test_state_change(self):
"""Test if the state changes at next setting/rising.""" """Test if the state changes at next setting/rising."""
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, { setup_component(self.hass, sun.DOMAIN, {
sun.DOMAIN: {sun.CONF_ELEVATION: 0}}) sun.DOMAIN: {sun.CONF_ELEVATION: 0}})
if sun.is_on(self.hass): self.hass.block_till_done()
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)
test_time = dt_util.parse_datetime(
self.hass.states.get(sun.ENTITY_ID)
.attributes[sun.STATE_ATTR_NEXT_RISING])
self.assertIsNotNone(test_time) 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, self.hass.bus.fire(ha.EVENT_TIME_CHANGED,
{ha.ATTR_NOW: test_time + timedelta(seconds=5)}) {ha.ATTR_NOW: test_time + timedelta(seconds=5)})
self.hass.block_till_done() 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): def test_norway_in_june(self):
"""Test location in Norway where the sun doesn't set in summer.""" """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}}) sun.DOMAIN: {sun.CONF_ELEVATION: 0}})
state = self.hass.states.get(sun.ENTITY_ID) state = self.hass.states.get(sun.ENTITY_ID)
assert state is not None 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) 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) datetime(2016, 7, 26, 22, 19, 1, tzinfo=dt_util.UTC)

View file

@ -25,6 +25,7 @@ from homeassistant.components import sun
import homeassistant.util.dt as dt_util import homeassistant.util.dt as dt_util
from tests.common import get_test_home_assistant from tests.common import get_test_home_assistant
from unittest.mock import patch
class TestEventHelpers(unittest.TestCase): class TestEventHelpers(unittest.TestCase):
@ -302,22 +303,25 @@ class TestEventHelpers(unittest.TestCase):
# Get next sunrise/sunset # Get next sunrise/sunset
astral = Astral() 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 mod = -1
while True: while True:
next_rising = (astral.sunrise_utc(utc_now + next_rising = (astral.sunrise_utc(
timedelta(days=mod), latitude, longitude)) utc_today + timedelta(days=mod), latitude, longitude))
if next_rising > utc_now: if next_rising > utc_now:
break break
mod += 1 mod += 1
# Track sunrise # Track sunrise
runs = [] runs = []
with patch('homeassistant.util.dt.utcnow', return_value=utc_now):
unsub = track_sunrise(self.hass, lambda: runs.append(1)) unsub = track_sunrise(self.hass, lambda: runs.append(1))
offset_runs = [] offset_runs = []
offset = timedelta(minutes=30) offset = timedelta(minutes=30)
with patch('homeassistant.util.dt.utcnow', return_value=utc_now):
unsub2 = track_sunrise(self.hass, lambda: offset_runs.append(1), unsub2 = track_sunrise(self.hass, lambda: offset_runs.append(1),
offset) offset)
@ -334,7 +338,7 @@ class TestEventHelpers(unittest.TestCase):
self._send_time_changed(next_rising + offset) self._send_time_changed(next_rising + offset)
self.hass.block_till_done() self.hass.block_till_done()
self.assertEqual(2, len(runs)) self.assertEqual(1, len(runs))
self.assertEqual(1, len(offset_runs)) self.assertEqual(1, len(offset_runs))
unsub() unsub()
@ -342,7 +346,7 @@ class TestEventHelpers(unittest.TestCase):
self._send_time_changed(next_rising + offset) self._send_time_changed(next_rising + offset)
self.hass.block_till_done() self.hass.block_till_done()
self.assertEqual(2, len(runs)) self.assertEqual(1, len(runs))
self.assertEqual(1, len(offset_runs)) self.assertEqual(1, len(offset_runs))
def test_track_sunset(self): def test_track_sunset(self):
@ -358,23 +362,27 @@ class TestEventHelpers(unittest.TestCase):
# Get next sunrise/sunset # Get next sunrise/sunset
astral = Astral() 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 mod = -1
while True: while True:
next_setting = (astral.sunset_utc(utc_now + next_setting = (astral.sunset_utc(
timedelta(days=mod), latitude, longitude)) utc_today + timedelta(days=mod), latitude, longitude))
if next_setting > utc_now: if next_setting > utc_now:
break break
mod += 1 mod += 1
# Track sunset # Track sunset
runs = [] runs = []
with patch('homeassistant.util.dt.utcnow', return_value=utc_now):
unsub = track_sunset(self.hass, lambda: runs.append(1)) unsub = track_sunset(self.hass, lambda: runs.append(1))
offset_runs = [] offset_runs = []
offset = timedelta(minutes=30) 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 # Run tests
self._send_time_changed(next_setting - offset) self._send_time_changed(next_setting - offset)
@ -389,7 +397,7 @@ class TestEventHelpers(unittest.TestCase):
self._send_time_changed(next_setting + offset) self._send_time_changed(next_setting + offset)
self.hass.block_till_done() self.hass.block_till_done()
self.assertEqual(2, len(runs)) self.assertEqual(1, len(runs))
self.assertEqual(1, len(offset_runs)) self.assertEqual(1, len(offset_runs))
unsub() unsub()
@ -397,7 +405,7 @@ class TestEventHelpers(unittest.TestCase):
self._send_time_changed(next_setting + offset) self._send_time_changed(next_setting + offset)
self.hass.block_till_done() self.hass.block_till_done()
self.assertEqual(2, len(runs)) self.assertEqual(1, len(runs))
self.assertEqual(1, len(offset_runs)) self.assertEqual(1, len(offset_runs))
def _send_time_changed(self, now): def _send_time_changed(self, now):

227
tests/helpers/test_sun.py Normal file
View file

@ -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