mirror of
https://github.com/home-assistant/core.git
synced 2025-07-24 21:57:51 +00:00
Quiet the chatty sun.sun (#23832)
* Split up method to allow caching event * Lower frequency of updates. * Code review patches. * Minor changes to test * Skip end of period at fixed multiple of delta. Improved documentation.
This commit is contained in:
parent
18149dcb8c
commit
9da74dda43
@ -6,10 +6,9 @@ from homeassistant.const import (
|
|||||||
CONF_ELEVATION, SUN_EVENT_SUNRISE, SUN_EVENT_SUNSET)
|
CONF_ELEVATION, SUN_EVENT_SUNRISE, SUN_EVENT_SUNSET)
|
||||||
from homeassistant.core import callback
|
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 async_track_point_in_utc_time
|
||||||
async_track_point_in_utc_time, async_track_utc_time_change)
|
|
||||||
from homeassistant.helpers.sun import (
|
from homeassistant.helpers.sun import (
|
||||||
get_astral_location, get_astral_event_next)
|
get_astral_location, get_location_astral_event_next)
|
||||||
from homeassistant.util import dt as dt_util
|
from homeassistant.util import dt as dt_util
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
@ -23,6 +22,7 @@ STATE_BELOW_HORIZON = 'below_horizon'
|
|||||||
|
|
||||||
STATE_ATTR_AZIMUTH = 'azimuth'
|
STATE_ATTR_AZIMUTH = 'azimuth'
|
||||||
STATE_ATTR_ELEVATION = 'elevation'
|
STATE_ATTR_ELEVATION = 'elevation'
|
||||||
|
STATE_ATTR_RISING = 'rising'
|
||||||
STATE_ATTR_NEXT_DAWN = 'next_dawn'
|
STATE_ATTR_NEXT_DAWN = 'next_dawn'
|
||||||
STATE_ATTR_NEXT_DUSK = 'next_dusk'
|
STATE_ATTR_NEXT_DUSK = 'next_dusk'
|
||||||
STATE_ATTR_NEXT_MIDNIGHT = 'next_midnight'
|
STATE_ATTR_NEXT_MIDNIGHT = 'next_midnight'
|
||||||
@ -30,6 +30,39 @@ 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'
|
||||||
|
|
||||||
|
# The algorithm used here is somewhat complicated. It aims to cut down
|
||||||
|
# the number of sensor updates over the day. It's documented best in
|
||||||
|
# the PR for the change, see the Discussion section of:
|
||||||
|
# https://github.com/home-assistant/home-assistant/pull/23832
|
||||||
|
|
||||||
|
|
||||||
|
# As documented in wikipedia: https://en.wikipedia.org/wiki/Twilight
|
||||||
|
# sun is:
|
||||||
|
# < -18° of horizon - all stars visible
|
||||||
|
PHASE_NIGHT = 'night'
|
||||||
|
# 18°-12° - some stars not visible
|
||||||
|
PHASE_ASTRONOMICAL_TWILIGHT = 'astronomical_twilight'
|
||||||
|
# 12°-6° - horizon visible
|
||||||
|
PHASE_NAUTICAL_TWILIGHT = 'nautical_twilight'
|
||||||
|
# 6°-0° - objects visible
|
||||||
|
PHASE_TWILIGHT = 'twilight'
|
||||||
|
# 0°-10° above horizon, sun low on horizon
|
||||||
|
PHASE_SMALL_DAY = 'small_day'
|
||||||
|
# > 10° above horizon
|
||||||
|
PHASE_DAY = 'day'
|
||||||
|
|
||||||
|
# 4 mins is one degree of arc change of the sun on its circle.
|
||||||
|
# During the night and the middle of the day we don't update
|
||||||
|
# that much since it's not important.
|
||||||
|
_PHASE_UPDATES = {
|
||||||
|
PHASE_NIGHT: timedelta(minutes=4*5),
|
||||||
|
PHASE_ASTRONOMICAL_TWILIGHT: timedelta(minutes=4*2),
|
||||||
|
PHASE_NAUTICAL_TWILIGHT: timedelta(minutes=4*2),
|
||||||
|
PHASE_TWILIGHT: timedelta(minutes=4),
|
||||||
|
PHASE_SMALL_DAY: timedelta(minutes=2),
|
||||||
|
PHASE_DAY: timedelta(minutes=4),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
async def async_setup(hass, config):
|
async def async_setup(hass, config):
|
||||||
"""Track the state of the sun."""
|
"""Track the state of the sun."""
|
||||||
@ -37,10 +70,7 @@ async def async_setup(hass, config):
|
|||||||
_LOGGER.warning(
|
_LOGGER.warning(
|
||||||
"Elevation is now configured in home assistant core. "
|
"Elevation is now configured in home assistant core. "
|
||||||
"See https://home-assistant.io/docs/configuration/basic/")
|
"See https://home-assistant.io/docs/configuration/basic/")
|
||||||
|
Sun(hass, get_astral_location(hass))
|
||||||
sun = Sun(hass, get_astral_location(hass))
|
|
||||||
sun.point_in_time_listener(dt_util.utcnow())
|
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
@ -57,8 +87,10 @@ class Sun(Entity):
|
|||||||
self.next_dawn = self.next_dusk = None
|
self.next_dawn = self.next_dusk = None
|
||||||
self.next_midnight = self.next_noon = None
|
self.next_midnight = self.next_noon = None
|
||||||
self.solar_elevation = self.solar_azimuth = None
|
self.solar_elevation = self.solar_azimuth = None
|
||||||
|
self.rising = self.phase = None
|
||||||
|
|
||||||
async_track_utc_time_change(hass, self.timer_update, second=30)
|
self._next_change = None
|
||||||
|
self.update_events(dt_util.utcnow())
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def name(self):
|
def name(self):
|
||||||
@ -83,57 +115,110 @@ class Sun(Entity):
|
|||||||
STATE_ATTR_NEXT_NOON: self.next_noon.isoformat(),
|
STATE_ATTR_NEXT_NOON: self.next_noon.isoformat(),
|
||||||
STATE_ATTR_NEXT_RISING: self.next_rising.isoformat(),
|
STATE_ATTR_NEXT_RISING: self.next_rising.isoformat(),
|
||||||
STATE_ATTR_NEXT_SETTING: self.next_setting.isoformat(),
|
STATE_ATTR_NEXT_SETTING: self.next_setting.isoformat(),
|
||||||
STATE_ATTR_ELEVATION: round(self.solar_elevation, 2),
|
STATE_ATTR_ELEVATION: self.solar_elevation,
|
||||||
STATE_ATTR_AZIMUTH: round(self.solar_azimuth, 2)
|
STATE_ATTR_AZIMUTH: self.solar_azimuth,
|
||||||
|
STATE_ATTR_RISING: self.rising,
|
||||||
}
|
}
|
||||||
|
|
||||||
@property
|
def _check_event(self, utc_point_in_time, event, before):
|
||||||
def next_change(self):
|
next_utc = get_location_astral_event_next(
|
||||||
"""Datetime when the next change to the state is."""
|
self.location, event, utc_point_in_time)
|
||||||
return min(self.next_dawn, self.next_dusk, self.next_midnight,
|
if next_utc < self._next_change:
|
||||||
self.next_noon, self.next_rising, self.next_setting)
|
self._next_change = next_utc
|
||||||
|
self.phase = before
|
||||||
|
return next_utc
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def update_as_of(self, utc_point_in_time):
|
def update_events(self, utc_point_in_time):
|
||||||
"""Update the attributes containing solar events."""
|
"""Update the attributes containing solar events."""
|
||||||
self.next_dawn = get_astral_event_next(
|
self._next_change = utc_point_in_time + timedelta(days=400)
|
||||||
self.hass, 'dawn', utc_point_in_time)
|
|
||||||
self.next_dusk = get_astral_event_next(
|
# Work our way around the solar cycle, figure out the next
|
||||||
self.hass, 'dusk', utc_point_in_time)
|
# phase. Some of these are stored.
|
||||||
self.next_midnight = get_astral_event_next(
|
self.location.solar_depression = 'astronomical'
|
||||||
self.hass, 'solar_midnight', utc_point_in_time)
|
self._check_event(utc_point_in_time, 'dawn', PHASE_NIGHT)
|
||||||
self.next_noon = get_astral_event_next(
|
self.location.solar_depression = 'nautical'
|
||||||
self.hass, 'solar_noon', utc_point_in_time)
|
self._check_event(
|
||||||
self.next_rising = get_astral_event_next(
|
utc_point_in_time, 'dawn', PHASE_ASTRONOMICAL_TWILIGHT)
|
||||||
self.hass, SUN_EVENT_SUNRISE, utc_point_in_time)
|
self.location.solar_depression = 'civil'
|
||||||
self.next_setting = get_astral_event_next(
|
self.next_dawn = self._check_event(
|
||||||
self.hass, SUN_EVENT_SUNSET, utc_point_in_time)
|
utc_point_in_time, 'dawn', PHASE_NAUTICAL_TWILIGHT)
|
||||||
|
self.next_rising = self._check_event(
|
||||||
|
utc_point_in_time, SUN_EVENT_SUNRISE, PHASE_TWILIGHT)
|
||||||
|
self.location.solar_depression = -10
|
||||||
|
self._check_event(utc_point_in_time, 'dawn', PHASE_SMALL_DAY)
|
||||||
|
self.next_noon = self._check_event(
|
||||||
|
utc_point_in_time, 'solar_noon', None)
|
||||||
|
self._check_event(utc_point_in_time, 'dusk', PHASE_DAY)
|
||||||
|
self.next_setting = self._check_event(
|
||||||
|
utc_point_in_time, SUN_EVENT_SUNSET, PHASE_SMALL_DAY)
|
||||||
|
self.location.solar_depression = 'civil'
|
||||||
|
self.next_dusk = self._check_event(
|
||||||
|
utc_point_in_time, 'dusk', PHASE_TWILIGHT)
|
||||||
|
self.location.solar_depression = 'nautical'
|
||||||
|
self._check_event(
|
||||||
|
utc_point_in_time, 'dusk', PHASE_NAUTICAL_TWILIGHT)
|
||||||
|
self.location.solar_depression = 'astronomical'
|
||||||
|
self._check_event(
|
||||||
|
utc_point_in_time, 'dusk', PHASE_ASTRONOMICAL_TWILIGHT)
|
||||||
|
self.next_midnight = self._check_event(
|
||||||
|
utc_point_in_time, 'solar_midnight', None)
|
||||||
|
|
||||||
|
# if the event was solar midday or midnight, phase will now
|
||||||
|
# be None. Solar noon doesn't always happen when the sun is
|
||||||
|
# even in the day at the poles, so we can't rely on it.
|
||||||
|
# Need to calculate phase if next is noon or midnight
|
||||||
|
if self.phase is None:
|
||||||
|
elevation = self.location.solar_elevation(self._next_change)
|
||||||
|
if elevation >= 10:
|
||||||
|
self.phase = PHASE_DAY
|
||||||
|
elif elevation >= 0:
|
||||||
|
self.phase = PHASE_SMALL_DAY
|
||||||
|
elif elevation >= -6:
|
||||||
|
self.phase = PHASE_TWILIGHT
|
||||||
|
elif elevation >= -12:
|
||||||
|
self.phase = PHASE_NAUTICAL_TWILIGHT
|
||||||
|
elif elevation >= -18:
|
||||||
|
self.phase = PHASE_ASTRONOMICAL_TWILIGHT
|
||||||
|
else:
|
||||||
|
self.phase = PHASE_NIGHT
|
||||||
|
|
||||||
|
self.rising = self.next_noon < self.next_midnight
|
||||||
|
|
||||||
|
_LOGGER.debug(
|
||||||
|
"sun phase_update@%s: phase=%s",
|
||||||
|
utc_point_in_time.isoformat(),
|
||||||
|
self.phase,
|
||||||
|
)
|
||||||
|
self.update_sun_position(utc_point_in_time)
|
||||||
|
|
||||||
|
# Set timer for the next solar event
|
||||||
|
async_track_point_in_utc_time(
|
||||||
|
self.hass, self.update_events,
|
||||||
|
self._next_change)
|
||||||
|
_LOGGER.debug("next time: %s", self._next_change.isoformat())
|
||||||
|
|
||||||
@callback
|
@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."""
|
||||||
self.solar_azimuth = self.location.solar_azimuth(utc_point_in_time)
|
self.solar_azimuth = round(
|
||||||
self.solar_elevation = self.location.solar_elevation(utc_point_in_time)
|
self.location.solar_azimuth(utc_point_in_time), 2)
|
||||||
|
self.solar_elevation = round(
|
||||||
|
self.location.solar_elevation(utc_point_in_time), 2)
|
||||||
|
|
||||||
@callback
|
_LOGGER.debug(
|
||||||
def point_in_time_listener(self, now):
|
"sun position_update@%s: elevation=%s azimuth=%s",
|
||||||
"""Run when the state of the sun has changed."""
|
utc_point_in_time.isoformat(),
|
||||||
self.update_sun_position(now)
|
self.solar_elevation, self.solar_azimuth
|
||||||
self.update_as_of(now)
|
)
|
||||||
self.async_write_ha_state()
|
self.async_write_ha_state()
|
||||||
_LOGGER.debug("sun point_in_time_listener@%s: %s, %s",
|
|
||||||
now, self.state, self.state_attributes)
|
|
||||||
|
|
||||||
# Schedule next update at next_change+1 second so sun state has changed
|
# Next update as per the current phase
|
||||||
|
delta = _PHASE_UPDATES[self.phase]
|
||||||
|
# if the next update is within 1.25 of the next
|
||||||
|
# position update just drop it
|
||||||
|
if utc_point_in_time + delta*1.25 > self._next_change:
|
||||||
|
return
|
||||||
async_track_point_in_utc_time(
|
async_track_point_in_utc_time(
|
||||||
self.hass, self.point_in_time_listener,
|
self.hass, self.update_sun_position,
|
||||||
self.next_change + timedelta(seconds=1))
|
utc_point_in_time + delta)
|
||||||
_LOGGER.debug("next time: %s", self.next_change + timedelta(seconds=1))
|
|
||||||
|
|
||||||
@callback
|
|
||||||
def timer_update(self, time):
|
|
||||||
"""Needed to update solar elevation and azimuth."""
|
|
||||||
self.update_sun_position(time)
|
|
||||||
self.async_write_ha_state()
|
|
||||||
_LOGGER.debug("sun timer_update@%s: %s, %s",
|
|
||||||
time, self.state, self.state_attributes)
|
|
||||||
|
@ -43,9 +43,18 @@ def get_astral_event_next(
|
|||||||
utc_point_in_time: Optional[datetime.datetime] = None,
|
utc_point_in_time: Optional[datetime.datetime] = None,
|
||||||
offset: Optional[datetime.timedelta] = None) -> datetime.datetime:
|
offset: Optional[datetime.timedelta] = None) -> datetime.datetime:
|
||||||
"""Calculate the next specified solar event."""
|
"""Calculate the next specified solar event."""
|
||||||
from astral import AstralError
|
|
||||||
|
|
||||||
location = get_astral_location(hass)
|
location = get_astral_location(hass)
|
||||||
|
return get_location_astral_event_next(
|
||||||
|
location, event, utc_point_in_time, offset)
|
||||||
|
|
||||||
|
|
||||||
|
@callback
|
||||||
|
def get_location_astral_event_next(
|
||||||
|
location: 'astral.Location', event: str,
|
||||||
|
utc_point_in_time: Optional[datetime.datetime] = None,
|
||||||
|
offset: Optional[datetime.timedelta] = None) -> datetime.datetime:
|
||||||
|
"""Calculate the next specified solar event."""
|
||||||
|
from astral import AstralError
|
||||||
|
|
||||||
if offset is None:
|
if offset is None:
|
||||||
offset = datetime.timedelta()
|
offset = datetime.timedelta()
|
||||||
|
@ -1,153 +1,177 @@
|
|||||||
"""The tests for the Sun component."""
|
"""The tests for the Sun component."""
|
||||||
# pylint: disable=protected-access
|
from datetime import datetime, timedelta
|
||||||
import unittest
|
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
from datetime import timedelta, datetime
|
|
||||||
|
|
||||||
from homeassistant.setup import setup_component
|
from pytest import mark
|
||||||
|
|
||||||
|
import homeassistant.components.sun as sun
|
||||||
import homeassistant.core as ha
|
import homeassistant.core as ha
|
||||||
import homeassistant.util.dt as dt_util
|
import homeassistant.util.dt as dt_util
|
||||||
import homeassistant.components.sun as sun
|
from homeassistant.const import EVENT_STATE_CHANGED
|
||||||
|
from homeassistant.setup import async_setup_component
|
||||||
from tests.common import get_test_home_assistant
|
|
||||||
|
|
||||||
|
|
||||||
# pylint: disable=invalid-name
|
async def test_setting_rising(hass):
|
||||||
class TestSun(unittest.TestCase):
|
"""Test retrieving sun setting and rising."""
|
||||||
"""Test the sun module."""
|
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):
|
||||||
|
await async_setup_component(hass, sun.DOMAIN, {
|
||||||
|
sun.DOMAIN: {sun.CONF_ELEVATION: 0}})
|
||||||
|
|
||||||
def setUp(self):
|
await hass.async_block_till_done()
|
||||||
"""Set up things to be run when tests are started."""
|
state = hass.states.get(sun.ENTITY_ID)
|
||||||
self.hass = get_test_home_assistant()
|
|
||||||
|
|
||||||
def tearDown(self):
|
from astral import Astral
|
||||||
"""Stop everything that was started."""
|
|
||||||
self.hass.stop()
|
|
||||||
|
|
||||||
def test_setting_rising(self):
|
astral = Astral()
|
||||||
"""Test retrieving sun setting and rising."""
|
utc_today = utc_now.date()
|
||||||
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()
|
latitude = hass.config.latitude
|
||||||
state = self.hass.states.get(sun.ENTITY_ID)
|
longitude = hass.config.longitude
|
||||||
|
|
||||||
from astral import Astral
|
mod = -1
|
||||||
|
while True:
|
||||||
|
next_dawn = (astral.dawn_utc(
|
||||||
|
utc_today + timedelta(days=mod), latitude, longitude))
|
||||||
|
if next_dawn > utc_now:
|
||||||
|
break
|
||||||
|
mod += 1
|
||||||
|
|
||||||
astral = Astral()
|
mod = -1
|
||||||
utc_today = utc_now.date()
|
while True:
|
||||||
|
next_dusk = (astral.dusk_utc(
|
||||||
|
utc_today + timedelta(days=mod), latitude, longitude))
|
||||||
|
if next_dusk > utc_now:
|
||||||
|
break
|
||||||
|
mod += 1
|
||||||
|
|
||||||
latitude = self.hass.config.latitude
|
mod = -1
|
||||||
longitude = self.hass.config.longitude
|
while True:
|
||||||
|
next_midnight = (astral.solar_midnight_utc(
|
||||||
|
utc_today + timedelta(days=mod), longitude))
|
||||||
|
if next_midnight > utc_now:
|
||||||
|
break
|
||||||
|
mod += 1
|
||||||
|
|
||||||
mod = -1
|
mod = -1
|
||||||
while True:
|
while True:
|
||||||
next_dawn = (astral.dawn_utc(
|
next_noon = (astral.solar_noon_utc(
|
||||||
utc_today + timedelta(days=mod), latitude, longitude))
|
utc_today + timedelta(days=mod), longitude))
|
||||||
if next_dawn > utc_now:
|
if next_noon > utc_now:
|
||||||
break
|
break
|
||||||
mod += 1
|
mod += 1
|
||||||
|
|
||||||
mod = -1
|
mod = -1
|
||||||
while True:
|
while True:
|
||||||
next_dusk = (astral.dusk_utc(
|
next_rising = (astral.sunrise_utc(
|
||||||
utc_today + timedelta(days=mod), latitude, longitude))
|
utc_today + timedelta(days=mod), latitude, longitude))
|
||||||
if next_dusk > utc_now:
|
if next_rising > utc_now:
|
||||||
break
|
break
|
||||||
mod += 1
|
mod += 1
|
||||||
|
|
||||||
mod = -1
|
mod = -1
|
||||||
while True:
|
while True:
|
||||||
next_midnight = (astral.solar_midnight_utc(
|
next_setting = (astral.sunset_utc(
|
||||||
utc_today + timedelta(days=mod), longitude))
|
utc_today + timedelta(days=mod), latitude, longitude))
|
||||||
if next_midnight > utc_now:
|
if next_setting > utc_now:
|
||||||
break
|
break
|
||||||
mod += 1
|
mod += 1
|
||||||
|
|
||||||
mod = -1
|
assert next_dawn == dt_util.parse_datetime(
|
||||||
while True:
|
state.attributes[sun.STATE_ATTR_NEXT_DAWN])
|
||||||
next_noon = (astral.solar_noon_utc(
|
assert next_dusk == dt_util.parse_datetime(
|
||||||
utc_today + timedelta(days=mod), longitude))
|
state.attributes[sun.STATE_ATTR_NEXT_DUSK])
|
||||||
if next_noon > utc_now:
|
assert next_midnight == dt_util.parse_datetime(
|
||||||
break
|
state.attributes[sun.STATE_ATTR_NEXT_MIDNIGHT])
|
||||||
mod += 1
|
assert next_noon == dt_util.parse_datetime(
|
||||||
|
state.attributes[sun.STATE_ATTR_NEXT_NOON])
|
||||||
|
assert next_rising == dt_util.parse_datetime(
|
||||||
|
state.attributes[sun.STATE_ATTR_NEXT_RISING])
|
||||||
|
assert next_setting == dt_util.parse_datetime(
|
||||||
|
state.attributes[sun.STATE_ATTR_NEXT_SETTING])
|
||||||
|
|
||||||
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
|
async def test_state_change(hass):
|
||||||
while True:
|
"""Test if the state changes at next setting/rising."""
|
||||||
next_setting = (astral.sunset_utc(
|
now = datetime(2016, 6, 1, 8, 0, 0, tzinfo=dt_util.UTC)
|
||||||
utc_today + timedelta(days=mod), latitude, longitude))
|
with patch('homeassistant.helpers.condition.dt_util.utcnow',
|
||||||
if next_setting > utc_now:
|
return_value=now):
|
||||||
break
|
await async_setup_component(hass, sun.DOMAIN, {
|
||||||
mod += 1
|
sun.DOMAIN: {sun.CONF_ELEVATION: 0}})
|
||||||
|
|
||||||
assert next_dawn == dt_util.parse_datetime(
|
await hass.async_block_till_done()
|
||||||
state.attributes[sun.STATE_ATTR_NEXT_DAWN])
|
|
||||||
assert next_dusk == dt_util.parse_datetime(
|
|
||||||
state.attributes[sun.STATE_ATTR_NEXT_DUSK])
|
|
||||||
assert next_midnight == dt_util.parse_datetime(
|
|
||||||
state.attributes[sun.STATE_ATTR_NEXT_MIDNIGHT])
|
|
||||||
assert next_noon == dt_util.parse_datetime(
|
|
||||||
state.attributes[sun.STATE_ATTR_NEXT_NOON])
|
|
||||||
assert next_rising == dt_util.parse_datetime(
|
|
||||||
state.attributes[sun.STATE_ATTR_NEXT_RISING])
|
|
||||||
assert next_setting == dt_util.parse_datetime(
|
|
||||||
state.attributes[sun.STATE_ATTR_NEXT_SETTING])
|
|
||||||
|
|
||||||
def test_state_change(self):
|
test_time = dt_util.parse_datetime(
|
||||||
"""Test if the state changes at next setting/rising."""
|
hass.states.get(sun.ENTITY_ID)
|
||||||
now = datetime(2016, 6, 1, 8, 0, 0, tzinfo=dt_util.UTC)
|
.attributes[sun.STATE_ATTR_NEXT_RISING])
|
||||||
with patch('homeassistant.helpers.condition.dt_util.utcnow',
|
assert test_time is not None
|
||||||
return_value=now):
|
|
||||||
setup_component(self.hass, sun.DOMAIN, {
|
|
||||||
sun.DOMAIN: {sun.CONF_ELEVATION: 0}})
|
|
||||||
|
|
||||||
self.hass.block_till_done()
|
assert sun.STATE_BELOW_HORIZON == \
|
||||||
|
hass.states.get(sun.ENTITY_ID).state
|
||||||
|
|
||||||
test_time = dt_util.parse_datetime(
|
hass.bus.async_fire(
|
||||||
self.hass.states.get(sun.ENTITY_ID)
|
ha.EVENT_TIME_CHANGED,
|
||||||
.attributes[sun.STATE_ATTR_NEXT_RISING])
|
{ha.ATTR_NOW: test_time + timedelta(seconds=5)})
|
||||||
assert test_time is not None
|
|
||||||
|
|
||||||
assert sun.STATE_BELOW_HORIZON == \
|
await hass.async_block_till_done()
|
||||||
self.hass.states.get(sun.ENTITY_ID).state
|
|
||||||
|
|
||||||
self.hass.bus.fire(ha.EVENT_TIME_CHANGED,
|
assert sun.STATE_ABOVE_HORIZON == \
|
||||||
{ha.ATTR_NOW: test_time + timedelta(seconds=5)})
|
hass.states.get(sun.ENTITY_ID).state
|
||||||
|
|
||||||
self.hass.block_till_done()
|
|
||||||
|
|
||||||
assert sun.STATE_ABOVE_HORIZON == \
|
async def test_norway_in_june(hass):
|
||||||
self.hass.states.get(sun.ENTITY_ID).state
|
"""Test location in Norway where the sun doesn't set in summer."""
|
||||||
|
hass.config.latitude = 69.6
|
||||||
|
hass.config.longitude = 18.8
|
||||||
|
|
||||||
def test_norway_in_june(self):
|
june = datetime(2016, 6, 1, tzinfo=dt_util.UTC)
|
||||||
"""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)
|
with patch('homeassistant.helpers.condition.dt_util.utcnow',
|
||||||
|
return_value=june):
|
||||||
|
assert await async_setup_component(hass, sun.DOMAIN, {
|
||||||
|
sun.DOMAIN: {sun.CONF_ELEVATION: 0}})
|
||||||
|
|
||||||
with patch('homeassistant.helpers.condition.dt_util.utcnow',
|
state = hass.states.get(sun.ENTITY_ID)
|
||||||
return_value=june):
|
assert state is not None
|
||||||
assert setup_component(self.hass, sun.DOMAIN, {
|
|
||||||
sun.DOMAIN: {sun.CONF_ELEVATION: 0}})
|
|
||||||
|
|
||||||
state = self.hass.states.get(sun.ENTITY_ID)
|
assert dt_util.parse_datetime(
|
||||||
assert state is not None
|
state.attributes[sun.STATE_ATTR_NEXT_RISING]) == \
|
||||||
|
datetime(2016, 7, 25, 23, 23, 39, tzinfo=dt_util.UTC)
|
||||||
|
assert dt_util.parse_datetime(
|
||||||
|
state.attributes[sun.STATE_ATTR_NEXT_SETTING]) == \
|
||||||
|
datetime(2016, 7, 26, 22, 19, 1, tzinfo=dt_util.UTC)
|
||||||
|
|
||||||
assert dt_util.parse_datetime(
|
|
||||||
state.attributes[sun.STATE_ATTR_NEXT_RISING]) == \
|
@mark.skip
|
||||||
datetime(2016, 7, 25, 23, 23, 39, tzinfo=dt_util.UTC)
|
async def test_state_change_count(hass):
|
||||||
assert dt_util.parse_datetime(
|
"""Count the number of state change events in a location."""
|
||||||
state.attributes[sun.STATE_ATTR_NEXT_SETTING]) == \
|
# Skipped because it's a bit slow. Has been validated with
|
||||||
datetime(2016, 7, 26, 22, 19, 1, tzinfo=dt_util.UTC)
|
# multiple lattitudes and dates
|
||||||
|
hass.config.latitude = 10
|
||||||
|
hass.config.longitude = 0
|
||||||
|
|
||||||
|
now = datetime(2016, 6, 1, tzinfo=dt_util.UTC)
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
'homeassistant.helpers.condition.dt_util.utcnow',
|
||||||
|
return_value=now):
|
||||||
|
assert await async_setup_component(hass, sun.DOMAIN, {
|
||||||
|
sun.DOMAIN: {sun.CONF_ELEVATION: 0}})
|
||||||
|
|
||||||
|
events = []
|
||||||
|
@ha.callback
|
||||||
|
def state_change_listener(event):
|
||||||
|
if event.data.get('entity_id') == 'sun.sun':
|
||||||
|
events.append(event)
|
||||||
|
hass.bus.async_listen(EVENT_STATE_CHANGED, state_change_listener)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
for _ in range(24*60*60):
|
||||||
|
now += timedelta(seconds=1)
|
||||||
|
hass.bus.async_fire(
|
||||||
|
ha.EVENT_TIME_CHANGED,
|
||||||
|
{ha.ATTR_NOW: now})
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert len(events) < 721
|
||||||
|
Loading…
x
Reference in New Issue
Block a user