def update_as_of(self, utc_time): """Update the attributes containing solar events.""" # Always need to update next_rising and next_setting so state can be # determined. for a, e in [(STATE_ATTR_NEXT_RISING, 'sunrise'), (STATE_ATTR_NEXT_SETTING, 'sunset')]: setattr(self, a, get_astral_event_next(self.hass, e, utc_time)) # Only need to update remaining properties if they will be reported # in attributes. for a, e in [(STATE_ATTR_NEXT_DAWN, 'dawn'), (STATE_ATTR_NEXT_DUSK, 'dusk'), (STATE_ATTR_NEXT_MIDNIGHT, 'solar_midnight'), (STATE_ATTR_NEXT_NOON, 'solar_noon')]: if a in self._monitored_condtions: setattr(self, a, get_astral_event_next(self.hass, e, utc_time)) for a, e in [(STATE_ATTR_SUNRISE, 'sunrise'), (STATE_ATTR_SUNSET, 'sunset')]: if a in self._monitored_condtions: setattr(self, a, get_astral_event_date(self.hass, e, utc_time)) for a, d in [(STATE_ATTR_DAYLIGHT, 0), (STATE_ATTR_PREV_DAYLIGHT, -1), (STATE_ATTR_NEXT_DAYLIGHT, 1)]: if a in self._monitored_condtions: dl = get_astral_event_date(self.hass, 'daylight', utc_time + timedelta(days=d)) setattr(self, a, (dl[1] - dl[0]).total_seconds())
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
async def test_norwegian_case_winter(hass, freezer, hass_tz_info): """Test location in Norway where the sun doesn't set in summer.""" hass.config.latitude = 69.6 hass.config.longitude = 18.8 test_time = datetime(2010, 1, 1, tzinfo=hass_tz_info) sunrise = dt_util.as_local( get_astral_event_next(hass, "sunrise", dt_util.as_utc(test_time))) sunset = dt_util.as_local( get_astral_event_next(hass, "sunset", dt_util.as_utc(test_time))) config = { "binary_sensor": [{ "platform": "tod", "name": "Day", "after": "sunrise", "before": "sunset", }] } entity_id = "binary_sensor.day" freezer.move_to(test_time) await async_setup_component(hass, "binary_sensor", config) await hass.async_block_till_done() state = hass.states.get(entity_id) assert state.state == STATE_OFF freezer.move_to(sunrise + timedelta(seconds=-1)) hass.bus.async_fire(ha.EVENT_TIME_CHANGED, {ha.ATTR_NOW: dt_util.utcnow()}) await hass.async_block_till_done() state = hass.states.get(entity_id) assert state.state == STATE_OFF freezer.move_to(sunrise) hass.bus.async_fire(ha.EVENT_TIME_CHANGED, {ha.ATTR_NOW: dt_util.utcnow()}) await hass.async_block_till_done() state = hass.states.get(entity_id) assert state.state == STATE_ON freezer.move_to(sunrise + timedelta(seconds=1)) hass.bus.async_fire(ha.EVENT_TIME_CHANGED, {ha.ATTR_NOW: dt_util.utcnow()}) await hass.async_block_till_done() state = hass.states.get(entity_id) assert state.state == STATE_ON freezer.move_to(sunset + timedelta(seconds=-1)) hass.bus.async_fire(ha.EVENT_TIME_CHANGED, {ha.ATTR_NOW: dt_util.utcnow()}) await hass.async_block_till_done() state = hass.states.get(entity_id) assert state.state == STATE_ON freezer.move_to(sunset) hass.bus.async_fire(ha.EVENT_TIME_CHANGED, {ha.ATTR_NOW: dt_util.utcnow()}) await hass.async_block_till_done() state = hass.states.get(entity_id) assert state.state == STATE_OFF freezer.move_to(sunset + timedelta(seconds=1)) hass.bus.async_fire(ha.EVENT_TIME_CHANGED, {ha.ATTR_NOW: dt_util.utcnow()}) await hass.async_block_till_done() state = hass.states.get(entity_id) assert state.state == STATE_OFF
def test_norway_in_june(hass): """Test location in Norway where the sun doesn't set in summer.""" hass.config.latitude = 69.6 hass.config.longitude = 18.8 june = datetime(2016, 6, 1, tzinfo=dt_util.UTC) print( sun.get_astral_event_date(hass, SUN_EVENT_SUNRISE, datetime(2017, 7, 25))) print( sun.get_astral_event_date(hass, SUN_EVENT_SUNSET, datetime(2017, 7, 25))) print( sun.get_astral_event_date(hass, SUN_EVENT_SUNRISE, datetime(2017, 7, 26))) print( sun.get_astral_event_date(hass, SUN_EVENT_SUNSET, datetime(2017, 7, 26))) assert sun.get_astral_event_next(hass, SUN_EVENT_SUNRISE, june) \ == datetime(2016, 7, 25, 23, 23, 39, tzinfo=dt_util.UTC) assert sun.get_astral_event_next(hass, SUN_EVENT_SUNSET, june) \ == datetime(2016, 7, 26, 22, 19, 1, tzinfo=dt_util.UTC) assert sun.get_astral_event_date(hass, SUN_EVENT_SUNRISE, june) \ is None assert sun.get_astral_event_date(hass, SUN_EVENT_SUNSET, june) \ is None
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, SUN_EVENT_SUNRISE, datetime(2017, 7, 25))) print(sun.get_astral_event_date(self.hass, SUN_EVENT_SUNSET, datetime(2017, 7, 25))) print(sun.get_astral_event_date(self.hass, SUN_EVENT_SUNRISE, datetime(2017, 7, 26))) print(sun.get_astral_event_date(self.hass, SUN_EVENT_SUNSET, datetime(2017, 7, 26))) assert sun.get_astral_event_next(self.hass, SUN_EVENT_SUNRISE, june) \ == datetime(2016, 7, 25, 23, 23, 39, tzinfo=dt_util.UTC) assert sun.get_astral_event_next(self.hass, SUN_EVENT_SUNSET, june) \ == datetime(2016, 7, 26, 22, 19, 1, tzinfo=dt_util.UTC) assert sun.get_astral_event_date(self.hass, SUN_EVENT_SUNRISE, june) \ is None assert sun.get_astral_event_date(self.hass, SUN_EVENT_SUNSET, june) \ is None
def update_as_of(self, utc_point_in_time): """Update the attributes containing solar events.""" # Always need to update next_rising and next_setting so state can be # determined. self.next_rising = get_astral_event_next(self.hass, 'sunrise', utc_point_in_time) self.next_setting = get_astral_event_next(self.hass, 'sunset', utc_point_in_time) # Only need to update remaining properties if they will be reported # in attributes. if STATE_ATTR_MAX_ELEVATION in self._attrs: self._attrs[STATE_ATTR_MAX_ELEVATION] = ( self.location.solar_elevation( get_astral_event_date(self.hass, 'solar_noon', utc_point_in_time))) for attr, event, func in [ (STATE_ATTR_NEXT_DAWN, 'dawn', get_astral_event_next), (STATE_ATTR_NEXT_DUSK, 'dusk', get_astral_event_next), (STATE_ATTR_NEXT_MIDNIGHT, 'solar_midnight', get_astral_event_next), (STATE_ATTR_NEXT_NOON, 'solar_noon', get_astral_event_next), (STATE_ATTR_SUNRISE, 'sunrise', get_astral_event_date), (STATE_ATTR_SUNSET, 'sunset', get_astral_event_date) ]: if attr in self._attrs: self._attrs[attr] = func(self.hass, event, utc_point_in_time) for attr, delta in [(STATE_ATTR_DAYLIGHT, 0), (STATE_ATTR_PREV_DAYLIGHT, -1), (STATE_ATTR_NEXT_DAYLIGHT, 1)]: if attr in self._attrs: daylight = get_astral_event_date( self.hass, 'daylight', utc_point_in_time + timedelta(days=delta)) self._attrs[attr] = (daylight[1] - daylight[0]).total_seconds()
def _calculate_boudary_time(self): """Calculate internal absolute time boundaries.""" nowutc = dt_util.utcnow() # If after value is a sun event instead of absolute time if is_sun_event(self._after): # Calculate the today's event utc time or # if not available take next after_event_date = get_astral_event_date( self.hass, self._after, nowutc ) or get_astral_event_next(self.hass, self._after, nowutc) else: # Convert local time provided to UTC today # datetime.combine(date, time, tzinfo) is not supported # in python 3.5. The self._after is provided # with hass configured TZ not system wide after_event_date = self._naive_time_to_utc_datetime(self._after) self._time_after = after_event_date # If before value is a sun event instead of absolute time if is_sun_event(self._before): # Calculate the today's event utc time or if not available take # next before_event_date = get_astral_event_date( self.hass, self._before, nowutc ) or get_astral_event_next(self.hass, self._before, nowutc) # Before is earlier than after if before_event_date < after_event_date: # Take next day for before before_event_date = get_astral_event_next( self.hass, self._before, after_event_date ) else: # Convert local time provided to UTC today, see above before_event_date = self._naive_time_to_utc_datetime(self._before) # It is safe to add timedelta days=1 to UTC as there is no DST if before_event_date < after_event_date + self._after_offset: before_event_date += timedelta(days=1) self._time_before = before_event_date # We are calculating the _time_after value assuming that it will happen today # But that is not always true, e.g. after 23:00, before 12:00 and now is 10:00 # If _time_before and _time_after are ahead of nowutc: # _time_before is set to 12:00 next day # _time_after is set to 23:00 today # nowutc is set to 10:00 today if ( not is_sun_event(self._after) and self._time_after > nowutc and self._time_before > nowutc + timedelta(days=1) ): # remove one day from _time_before and _time_after self._time_after -= timedelta(days=1) self._time_before -= timedelta(days=1) # Add offset to utc boundaries according to the configuration self._time_after += self._after_offset self._time_before += self._before_offset
def _daylight_today(self) -> float: """Calculate duration in hours between sunrise and sunset today.""" today = dt_util.now() today = today.replace(hour=1) _LOGGER.debug("Calculating daylight for today: %s", today) sunrise = get_astral_event_next(self._hass, SUN_EVENT_SUNRISE, dt_util.as_utc(today)) sunset = get_astral_event_next(self._hass, SUN_EVENT_SUNSET, dt_util.as_utc(today)) daylight = sunset - sunrise daylight_in_hours = daylight.total_seconds() / 3600 _LOGGER.debug("Total daylight today: %.1f hours", daylight_in_hours) return daylight_in_hours
def _calculate_initial_boudary_time(self): """Calculate internal absolute time boudaries.""" nowutc = self.current_datetime # If after value is a sun event instead of absolute time if is_sun_event(self._after): # Calculate the today's event utc time or # if not available take next after_event_date = \ get_astral_event_date(self.hass, self._after, nowutc) or \ get_astral_event_next(self.hass, self._after, nowutc) else: # Convert local time provided to UTC today # datetime.combine(date, time, tzinfo) is not supported # in python 3.5. The self._after is provided # with hass configured TZ not system wide after_event_date = datetime.combine( nowutc, self._after.replace( tzinfo=self.hass.config.time_zone)).astimezone(tz=pytz.UTC) self._time_after = after_event_date # If before value is a sun event instead of absolute time if is_sun_event(self._before): # Calculate the today's event utc time or if not available take # next before_event_date = \ get_astral_event_date(self.hass, self._before, nowutc) or \ get_astral_event_next(self.hass, self._before, nowutc) # Before is earlier than after if before_event_date < after_event_date: # Take next day for before before_event_date = get_astral_event_next( self.hass, self._before, after_event_date) else: # Convert local time provided to UTC today, see above before_event_date = datetime.combine( nowutc, self._before.replace( tzinfo=self.hass.config.time_zone)).astimezone(tz=pytz.UTC) # It is safe to add timedelta days=1 to UTC as there is no DST if before_event_date < after_event_date + self._after_offset: before_event_date += timedelta(days=1) self._time_before = before_event_date # Add offset to utc boundaries according to the configuration self._time_after += self._after_offset self._time_before += self._before_offset
def sunset_automation_listener(now): """Handle points in time to execute actions.""" nonlocal remove remove = async_track_point_in_utc_time( hass, sunset_automation_listener, get_astral_event_next( hass, SUN_EVENT_SUNSET, offset=offset)) hass.async_run_job(action)
def sunset_automation_listener(now): """Handle points in time to execute actions.""" nonlocal remove remove = async_track_point_in_utc_time( hass, sunset_automation_listener, get_astral_event_next(hass, 'sunset', offset=offset)) hass.async_run_job(action)
def _listen_next_sun_event(self): """Set up the sun event listener.""" assert self._unsub_sun is None self._unsub_sun = async_track_point_in_utc_time( self.hass, self._handle_sun_event, get_astral_event_next(self.hass, self.event, offset=self.offset))
def check_light_on_dev_state_change(entity, old_state, new_state): """Handle tracked device state changes.""" lights_are_on = group.is_on(hass, light_group) light_needed = not (lights_are_on or is_up(hass)) # These variables are needed for the elif check now = dt_util.utcnow() start_point = calc_time_for_light_when_sunset() # Do we need lights? if light_needed: logger.info("Home coming event for %s. Turning lights on", entity) light.async_turn_on(hass, light_ids, profile=light_profile) # Are we in the time span were we would turn on the lights # if someone would be home? # Check this by seeing if current time is later then the point # in time when we would start putting the lights on. elif (start_point and start_point < now < get_astral_event_next(hass, 'sunset')): # Check for every light if it would be on if someone was home # when the fading in started and turn it on if so for index, light_id in enumerate(light_ids): if now > start_point + index * LIGHT_TRANSITION_TIME: light.async_turn_on(hass, light_id) else: # If this light didn't happen to be turned on yet so # will all the following then, break. break
def check_light_on_dev_state_change(entity, old_state, new_state): """Handle tracked device state changes.""" lights_are_on = group.is_on(light_group) light_needed = not (lights_are_on or is_up(hass)) # These variables are needed for the elif check now = dt_util.utcnow() start_point = calc_time_for_light_when_sunset() # Do we need lights? if light_needed: logger.info("Home coming event for %s. Turning lights on", entity) light.async_turn_on(light_ids, profile=light_profile) # Are we in the time span were we would turn on the lights # if someone would be home? # Check this by seeing if current time is later then the point # in time when we would start putting the lights on. elif (start_point and start_point < now < get_astral_event_next(hass, 'sunset')): # Check for every light if it would be on if someone was home # when the fading in started and turn it on if so for index, light_id in enumerate(light_ids): if now > start_point + index * LIGHT_TRANSITION_TIME: light.async_turn_on(light_id) else: # If this light didn't happen to be turned on yet so # will all the following then, break. break
def _turn_to_next_day(self): """Turn to to the next day.""" if is_sun_event(self._after): self._time_after = get_astral_event_next( self.hass, self._after, self._time_after - self._after_offset) self._time_after += self._after_offset else: # Offset is already there self._time_after += timedelta(days=1) if is_sun_event(self._before): self._time_before = get_astral_event_next( self.hass, self._before, self._time_before - self._before_offset) self._time_before += self._before_offset else: # Offset is already there self._time_before += timedelta(days=1)
def get_solar_event(solar_event, local_now, hass): """Returns the next occurring solar event. Local time.""" start_of_day_utc = dt_util.as_utc( datetime.combine(local_now.date(), time(hour=0, minute=0))) next_dawn = dt_util.as_local( get_astral_event_next(hass, solar_event, utc_point_in_time=start_of_day_utc)) return next_dawn
def calc_time_for_light_when_sunset(): """Calculate the time when to start fading lights in when sun sets. Returns None if no next_setting data available. Async friendly. """ next_setting = get_astral_event_next(hass, 'sunset') if not next_setting: return None return next_setting - LIGHT_TRANSITION_TIME * len(light_ids)
def calc_time_for_light_when_sunset(): """Calculate the time when to start fading lights in when sun sets. Returns None if no next_setting data available. Async friendly. """ next_setting = get_astral_event_next(hass, SUN_EVENT_SUNSET) if not next_setting: return None return next_setting - LIGHT_TRANSITION_TIME * len(light_ids)
async def test_from_sunset_to_sunrise(hass, freezer, hass_tz_info): """Test period from sunset to sunrise.""" test_time = datetime(2019, 1, 12, tzinfo=hass_tz_info) sunset = dt_util.as_local(get_astral_event_date(hass, "sunset", test_time)) sunrise = dt_util.as_local(get_astral_event_next(hass, "sunrise", sunset)) # assert sunset == sunrise config = { "binary_sensor": [ { "platform": "tod", "name": "Night", "after": "sunset", "before": "sunrise", } ] } entity_id = "binary_sensor.night" freezer.move_to(sunset + timedelta(seconds=-1)) await async_setup_component(hass, "binary_sensor", config) await hass.async_block_till_done() state = hass.states.get(entity_id) assert state.state == STATE_OFF freezer.move_to(sunset) async_fire_time_changed(hass, dt_util.utcnow()) await hass.async_block_till_done() state = hass.states.get(entity_id) assert state.state == STATE_ON freezer.move_to(sunset + timedelta(minutes=1)) async_fire_time_changed(hass, dt_util.utcnow()) await hass.async_block_till_done() state = hass.states.get(entity_id) assert state.state == STATE_ON freezer.move_to(sunrise + timedelta(minutes=-1)) async_fire_time_changed(hass, dt_util.utcnow()) await hass.async_block_till_done() state = hass.states.get(entity_id) assert state.state == STATE_ON freezer.move_to(sunrise) async_fire_time_changed(hass, dt_util.utcnow()) await hass.async_block_till_done() state = hass.states.get(entity_id) assert state.state == STATE_OFF freezer.move_to(sunrise + timedelta(minutes=1)) async_fire_time_changed(hass, dt_util.utcnow()) await hass.async_block_till_done() state = hass.states.get(entity_id) assert state.state == STATE_OFF
def update_as_of(self, utc_point_in_time): """Update the attributes containing solar events.""" self.next_dawn = get_astral_event_next( self.hass, 'dawn', utc_point_in_time) self.next_dusk = get_astral_event_next( self.hass, 'dusk', utc_point_in_time) self.next_midnight = get_astral_event_next( self.hass, 'solar_midnight', utc_point_in_time) self.next_noon = get_astral_event_next( self.hass, 'solar_noon', utc_point_in_time) self.next_rising = get_astral_event_next( self.hass, SUN_EVENT_SUNRISE, utc_point_in_time) self.next_setting = get_astral_event_next( self.hass, SUN_EVENT_SUNSET, utc_point_in_time)
def update_as_of(self, utc_point_in_time): """Update the attributes containing solar events.""" self.next_dawn = get_astral_event_next( self.hass, 'dawn', utc_point_in_time) self.next_dusk = get_astral_event_next( self.hass, 'dusk', utc_point_in_time) self.next_midnight = get_astral_event_next( self.hass, 'solar_midnight', utc_point_in_time) self.next_noon = get_astral_event_next( self.hass, 'solar_noon', utc_point_in_time) self.next_rising = get_astral_event_next( self.hass, 'sunrise', utc_point_in_time) self.next_setting = get_astral_event_next( self.hass, 'sunset', utc_point_in_time)
def check_light_on_dev_state_change(entity, old_state, new_state): """Handle tracked device state changes.""" lights_are_on = any_light_on() light_needed = not (lights_are_on or is_up(hass)) # These variables are needed for the elif check now = dt_util.utcnow() start_point = calc_time_for_light_when_sunset() # Do we need lights? if light_needed: logger.info("Home coming event for %s. Turning lights on", entity) hass.async_create_task( hass.services.async_call( DOMAIN_LIGHT, SERVICE_TURN_ON, {ATTR_ENTITY_ID: light_ids, ATTR_PROFILE: light_profile}, ) ) # Are we in the time span were we would turn on the lights # if someone would be home? # Check this by seeing if current time is later then the point # in time when we would start putting the lights on. elif start_point and start_point < now < get_astral_event_next( hass, SUN_EVENT_SUNSET ): # Check for every light if it would be on if someone was home # when the fading in started and turn it on if so for index, light_id in enumerate(light_ids): if now > start_point + index * LIGHT_TRANSITION_TIME: hass.async_create_task( hass.services.async_call( DOMAIN_LIGHT, SERVICE_TURN_ON, {ATTR_ENTITY_ID: light_id} ) ) else: # If this light didn't happen to be turned on yet so # will all the following then, break. break
def async_track_sunset(hass, action, offset=None): """Add a listener that will fire a specified offset from sunset daily.""" remove = None @callback def sunset_automation_listener(now): """Handle points in time to execute actions.""" nonlocal remove remove = async_track_point_in_utc_time( hass, sunset_automation_listener, get_astral_event_next(hass, 'sunset', offset=offset)) hass.async_run_job(action) remove = async_track_point_in_utc_time( hass, sunset_automation_listener, get_astral_event_next(hass, 'sunset', offset=offset)) def remove_listener(): """Remove sunset listener.""" remove() return remove_listener
def async_track_sunset(hass, action, offset=None): """Add a listener that will fire a specified offset from sunset daily.""" remove = None @callback def sunset_automation_listener(now): """Handle points in time to execute actions.""" nonlocal remove remove = async_track_point_in_utc_time( hass, sunset_automation_listener, get_astral_event_next( hass, SUN_EVENT_SUNSET, offset=offset)) hass.async_run_job(action) remove = async_track_point_in_utc_time( hass, sunset_automation_listener, get_astral_event_next( hass, SUN_EVENT_SUNSET, offset=offset)) def remove_listener(): """Remove sunset listener.""" remove() return remove_listener
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): assert next_dawn == sun.get_astral_event_next( self.hass, 'dawn') assert next_dusk == sun.get_astral_event_next( self.hass, 'dusk') assert next_midnight == sun.get_astral_event_next( self.hass, 'solar_midnight') assert next_noon == sun.get_astral_event_next( self.hass, 'solar_noon') assert next_rising == sun.get_astral_event_next( self.hass, SUN_EVENT_SUNRISE) assert next_setting == sun.get_astral_event_next( self.hass, SUN_EVENT_SUNSET)
def test_norwegian_case_summer(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 test_time = self.hass.config.time_zone.localize(datetime( 2010, 6, 1)).astimezone(pytz.UTC) sunrise = dt_util.as_local( get_astral_event_next(self.hass, "sunrise", dt_util.as_utc(test_time))) sunset = dt_util.as_local( get_astral_event_next(self.hass, "sunset", dt_util.as_utc(test_time))) config = { "binary_sensor": [{ "platform": "tod", "name": "Day", "after": "sunrise", "before": "sunset", }] } entity_id = "binary_sensor.day" testtime = test_time with patch( "homeassistant.components.tod.binary_sensor.dt_util.utcnow", return_value=testtime, ): setup_component(self.hass, "binary_sensor", config) self.hass.block_till_done() state = self.hass.states.get(entity_id) assert state.state == STATE_OFF testtime = sunrise + timedelta(seconds=-1) with patch( "homeassistant.components.tod.binary_sensor.dt_util.utcnow", return_value=testtime, ): self.hass.bus.fire(ha.EVENT_TIME_CHANGED, {ha.ATTR_NOW: testtime}) self.hass.block_till_done() state = self.hass.states.get(entity_id) assert state.state == STATE_OFF testtime = sunrise with patch( "homeassistant.components.tod.binary_sensor.dt_util.utcnow", return_value=testtime, ): self.hass.bus.fire(ha.EVENT_TIME_CHANGED, {ha.ATTR_NOW: testtime}) self.hass.block_till_done() state = self.hass.states.get(entity_id) assert state.state == STATE_ON testtime = sunrise + timedelta(seconds=1) with patch( "homeassistant.components.tod.binary_sensor.dt_util.utcnow", return_value=testtime, ): self.hass.bus.fire(ha.EVENT_TIME_CHANGED, {ha.ATTR_NOW: testtime}) self.hass.block_till_done() state = self.hass.states.get(entity_id) assert state.state == STATE_ON self.hass.block_till_done() testtime = sunset + timedelta(seconds=-1) with patch( "homeassistant.components.tod.binary_sensor.dt_util.utcnow", return_value=testtime, ): self.hass.bus.fire(ha.EVENT_TIME_CHANGED, {ha.ATTR_NOW: testtime}) self.hass.block_till_done() state = self.hass.states.get(entity_id) assert state.state == STATE_ON self.hass.block_till_done() testtime = sunset with patch( "homeassistant.components.tod.binary_sensor.dt_util.utcnow", return_value=testtime, ): self.hass.bus.fire(ha.EVENT_TIME_CHANGED, {ha.ATTR_NOW: testtime}) self.hass.block_till_done() state = self.hass.states.get(entity_id) assert state.state == STATE_OFF self.hass.block_till_done() testtime = sunset + timedelta(seconds=1) with patch( "homeassistant.components.tod.binary_sensor.dt_util.utcnow", return_value=testtime, ): self.hass.bus.fire(ha.EVENT_TIME_CHANGED, {ha.ATTR_NOW: testtime}) self.hass.block_till_done() state = self.hass.states.get(entity_id) assert state.state == STATE_OFF
def test_from_sunset_to_sunrise(self): """Test period from sunset to sunrise.""" test_time = self.hass.config.time_zone.localize( datetime(2019, 1, 12)).astimezone(pytz.UTC) sunset = dt_util.as_local(get_astral_event_date( self.hass, 'sunset', test_time)) sunrise = dt_util.as_local(get_astral_event_next( self.hass, 'sunrise', sunset)) # assert sunset == sunrise config = { 'binary_sensor': [ { 'platform': 'tod', 'name': 'Night', 'after': 'sunset', 'before': 'sunrise' } ] } entity_id = 'binary_sensor.night' testtime = sunset + timedelta(minutes=-1) with patch('homeassistant.components.tod.binary_sensor.dt_util.utcnow', return_value=testtime): setup_component(self.hass, 'binary_sensor', config) self.hass.block_till_done() state = self.hass.states.get(entity_id) assert state.state == STATE_OFF testtime = sunset with patch('homeassistant.components.tod.binary_sensor.dt_util.utcnow', return_value=testtime): self.hass.bus.fire(ha.EVENT_TIME_CHANGED, { ha.ATTR_NOW: testtime}) self.hass.block_till_done() state = self.hass.states.get(entity_id) assert state.state == STATE_ON testtime = sunset + timedelta(minutes=1) with patch('homeassistant.components.tod.binary_sensor.dt_util.utcnow', return_value=testtime): self.hass.bus.fire(ha.EVENT_TIME_CHANGED, { ha.ATTR_NOW: testtime}) self.hass.block_till_done() state = self.hass.states.get(entity_id) assert state.state == STATE_ON testtime = sunrise + timedelta(minutes=-1) with patch('homeassistant.components.tod.binary_sensor.dt_util.utcnow', return_value=testtime): self.hass.bus.fire(ha.EVENT_TIME_CHANGED, { ha.ATTR_NOW: testtime}) self.hass.block_till_done() state = self.hass.states.get(entity_id) assert state.state == STATE_ON testtime = sunrise with patch('homeassistant.components.tod.binary_sensor.dt_util.utcnow', return_value=testtime): self.hass.bus.fire(ha.EVENT_TIME_CHANGED, { ha.ATTR_NOW: testtime}) self.hass.block_till_done() state = self.hass.states.get(entity_id) self.hass.block_till_done() # assert state == "dupa" assert state.state == STATE_OFF testtime = sunrise + timedelta(minutes=1) with patch('homeassistant.components.tod.binary_sensor.dt_util.utcnow', return_value=testtime): self.hass.bus.fire(ha.EVENT_TIME_CHANGED, { ha.ATTR_NOW: testtime}) self.hass.block_till_done() state = self.hass.states.get(entity_id) assert state.state == STATE_OFF
def test_next_events(hass): """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 = hass.config.latitude longitude = 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): assert next_dawn == sun.get_astral_event_next(hass, "dawn") assert next_dusk == sun.get_astral_event_next(hass, "dusk") assert next_midnight == sun.get_astral_event_next( hass, "solar_midnight") assert next_noon == sun.get_astral_event_next(hass, "solar_noon") assert next_rising == sun.get_astral_event_next( hass, SUN_EVENT_SUNRISE) assert next_setting == sun.get_astral_event_next( hass, SUN_EVENT_SUNSET)
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_from_sunset_to_sunrise(self): """Test period from sunset to sunrise.""" test_time = self.hass.config.time_zone.localize(datetime( 2019, 1, 12)).astimezone(pytz.UTC) sunset = dt_util.as_local( get_astral_event_date(self.hass, 'sunset', test_time)) sunrise = dt_util.as_local( get_astral_event_next(self.hass, 'sunrise', sunset)) # assert sunset == sunrise config = { 'binary_sensor': [{ 'platform': 'tod', 'name': 'Night', 'after': 'sunset', 'before': 'sunrise' }] } entity_id = 'binary_sensor.night' testtime = sunset + timedelta(minutes=-1) with patch('homeassistant.components.tod.binary_sensor.dt_util.utcnow', return_value=testtime): setup_component(self.hass, 'binary_sensor', config) self.hass.block_till_done() state = self.hass.states.get(entity_id) assert state.state == STATE_OFF testtime = sunset with patch('homeassistant.components.tod.binary_sensor.dt_util.utcnow', return_value=testtime): self.hass.bus.fire(ha.EVENT_TIME_CHANGED, {ha.ATTR_NOW: testtime}) self.hass.block_till_done() state = self.hass.states.get(entity_id) assert state.state == STATE_ON testtime = sunset + timedelta(minutes=1) with patch('homeassistant.components.tod.binary_sensor.dt_util.utcnow', return_value=testtime): self.hass.bus.fire(ha.EVENT_TIME_CHANGED, {ha.ATTR_NOW: testtime}) self.hass.block_till_done() state = self.hass.states.get(entity_id) assert state.state == STATE_ON testtime = sunrise + timedelta(minutes=-1) with patch('homeassistant.components.tod.binary_sensor.dt_util.utcnow', return_value=testtime): self.hass.bus.fire(ha.EVENT_TIME_CHANGED, {ha.ATTR_NOW: testtime}) self.hass.block_till_done() state = self.hass.states.get(entity_id) assert state.state == STATE_ON testtime = sunrise with patch('homeassistant.components.tod.binary_sensor.dt_util.utcnow', return_value=testtime): self.hass.bus.fire(ha.EVENT_TIME_CHANGED, {ha.ATTR_NOW: testtime}) self.hass.block_till_done() state = self.hass.states.get(entity_id) self.hass.block_till_done() # assert state == "dupa" assert state.state == STATE_OFF testtime = sunrise + timedelta(minutes=1) with patch('homeassistant.components.tod.binary_sensor.dt_util.utcnow', return_value=testtime): self.hass.bus.fire(ha.EVENT_TIME_CHANGED, {ha.ATTR_NOW: testtime}) self.hass.block_till_done() state = self.hass.states.get(entity_id) assert state.state == STATE_OFF
def test_norwegian_case_winter(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 test_time = self.hass.config.time_zone.localize(datetime( 2010, 1, 1)).astimezone(pytz.UTC) sunrise = dt_util.as_local( get_astral_event_next(self.hass, 'sunrise', dt_util.as_utc(test_time))) sunset = dt_util.as_local( get_astral_event_next(self.hass, 'sunset', dt_util.as_utc(test_time))) config = { 'binary_sensor': [{ 'platform': 'tod', 'name': 'Day', 'after': 'sunrise', 'before': 'sunset' }] } entity_id = 'binary_sensor.day' testtime = test_time with patch('homeassistant.components.tod.binary_sensor.dt_util.utcnow', return_value=testtime): setup_component(self.hass, 'binary_sensor', config) self.hass.block_till_done() state = self.hass.states.get(entity_id) assert state.state == STATE_OFF testtime = sunrise + timedelta(seconds=-1) with patch('homeassistant.components.tod.binary_sensor.dt_util.utcnow', return_value=testtime): self.hass.bus.fire(ha.EVENT_TIME_CHANGED, {ha.ATTR_NOW: testtime}) self.hass.block_till_done() state = self.hass.states.get(entity_id) assert state.state == STATE_OFF testtime = sunrise with patch('homeassistant.components.tod.binary_sensor.dt_util.utcnow', return_value=testtime): self.hass.bus.fire(ha.EVENT_TIME_CHANGED, {ha.ATTR_NOW: testtime}) self.hass.block_till_done() state = self.hass.states.get(entity_id) assert state.state == STATE_ON testtime = sunrise + timedelta(seconds=1) with patch('homeassistant.components.tod.binary_sensor.dt_util.utcnow', return_value=testtime): self.hass.bus.fire(ha.EVENT_TIME_CHANGED, {ha.ATTR_NOW: testtime}) self.hass.block_till_done() state = self.hass.states.get(entity_id) assert state.state == STATE_ON self.hass.block_till_done() testtime = sunset + timedelta(seconds=-1) with patch('homeassistant.components.tod.binary_sensor.dt_util.utcnow', return_value=testtime): self.hass.bus.fire(ha.EVENT_TIME_CHANGED, {ha.ATTR_NOW: testtime}) self.hass.block_till_done() state = self.hass.states.get(entity_id) assert state.state == STATE_ON self.hass.block_till_done() testtime = sunset with patch('homeassistant.components.tod.binary_sensor.dt_util.utcnow', return_value=testtime): self.hass.bus.fire(ha.EVENT_TIME_CHANGED, {ha.ATTR_NOW: testtime}) self.hass.block_till_done() state = self.hass.states.get(entity_id) assert state.state == STATE_OFF self.hass.block_till_done() testtime = sunset + timedelta(seconds=1) with patch('homeassistant.components.tod.binary_sensor.dt_util.utcnow', return_value=testtime): self.hass.bus.fire(ha.EVENT_TIME_CHANGED, {ha.ATTR_NOW: testtime}) self.hass.block_till_done() state = self.hass.states.get(entity_id) assert state.state == STATE_OFF
def async_setup(hass, config): """Set up the triggers to control lights based on device presence.""" logger = logging.getLogger(__name__) device_tracker = get_component('device_tracker') group = get_component('group') light = get_component('light') conf = config[DOMAIN] disable_turn_off = conf.get(CONF_DISABLE_TURN_OFF) light_group = conf.get(CONF_LIGHT_GROUP, light.ENTITY_ID_ALL_LIGHTS) light_profile = conf.get(CONF_LIGHT_PROFILE) device_group = conf.get( CONF_DEVICE_GROUP, device_tracker.ENTITY_ID_ALL_DEVICES) device_entity_ids = group.get_entity_ids( hass, device_group, device_tracker.DOMAIN) if not device_entity_ids: logger.error("No devices found to track") return False # Get the light IDs from the specified group light_ids = group.get_entity_ids(hass, light_group, light.DOMAIN) if not light_ids: logger.error("No lights found to turn on") return False def calc_time_for_light_when_sunset(): """Calculate the time when to start fading lights in when sun sets. Returns None if no next_setting data available. Async friendly. """ next_setting = get_astral_event_next(hass, 'sunset') if not next_setting: return None return next_setting - LIGHT_TRANSITION_TIME * len(light_ids) def async_turn_on_before_sunset(light_id): """Turn on lights.""" if not device_tracker.is_on(hass) or light.is_on(hass, light_id): return light.async_turn_on(hass, light_id, transition=LIGHT_TRANSITION_TIME.seconds, profile=light_profile) def async_turn_on_factory(light_id): """Generate turn on callbacks as factory.""" @callback def async_turn_on_light(now): """Turn on specific light.""" async_turn_on_before_sunset(light_id) return async_turn_on_light # Track every time sun rises so we can schedule a time-based # pre-sun set event @callback def schedule_light_turn_on(now): """Turn on all the lights at the moment sun sets. We will schedule to have each light start after one another and slowly transition in. """ start_point = calc_time_for_light_when_sunset() if not start_point: return for index, light_id in enumerate(light_ids): async_track_point_in_utc_time( hass, async_turn_on_factory(light_id), start_point + index * LIGHT_TRANSITION_TIME) async_track_point_in_utc_time(hass, schedule_light_turn_on, get_astral_event_next(hass, 'sunrise')) # If the sun is already above horizon schedule the time-based pre-sun set # event. if is_up(hass): schedule_light_turn_on(None) @callback def check_light_on_dev_state_change(entity, old_state, new_state): """Handle tracked device state changes.""" lights_are_on = group.is_on(hass, light_group) light_needed = not (lights_are_on or is_up(hass)) # These variables are needed for the elif check now = dt_util.utcnow() start_point = calc_time_for_light_when_sunset() # Do we need lights? if light_needed: logger.info("Home coming event for %s. Turning lights on", entity) light.async_turn_on(hass, light_ids, profile=light_profile) # Are we in the time span were we would turn on the lights # if someone would be home? # Check this by seeing if current time is later then the point # in time when we would start putting the lights on. elif (start_point and start_point < now < get_astral_event_next(hass, 'sunset')): # Check for every light if it would be on if someone was home # when the fading in started and turn it on if so for index, light_id in enumerate(light_ids): if now > start_point + index * LIGHT_TRANSITION_TIME: light.async_turn_on(hass, light_id) else: # If this light didn't happen to be turned on yet so # will all the following then, break. break async_track_state_change( hass, device_entity_ids, check_light_on_dev_state_change, STATE_NOT_HOME, STATE_HOME) if disable_turn_off: return True @callback def turn_off_lights_when_all_leave(entity, old_state, new_state): """Handle device group state change.""" if not group.is_on(hass, light_group): return logger.info( "Everyone has left but there are lights on. Turning them off") light.async_turn_off(hass, light_ids) async_track_state_change( hass, device_group, turn_off_lights_when_all_leave, STATE_HOME, STATE_NOT_HOME) return True
def async_setup(hass, config): """Set up the triggers to control lights based on device presence.""" logger = logging.getLogger(__name__) device_tracker = get_component('device_tracker') group = get_component('group') light = get_component('light') conf = config[DOMAIN] disable_turn_off = conf.get(CONF_DISABLE_TURN_OFF) light_group = conf.get(CONF_LIGHT_GROUP, light.ENTITY_ID_ALL_LIGHTS) light_profile = conf.get(CONF_LIGHT_PROFILE) device_group = conf.get(CONF_DEVICE_GROUP, device_tracker.ENTITY_ID_ALL_DEVICES) device_entity_ids = group.get_entity_ids(hass, device_group, device_tracker.DOMAIN) if not device_entity_ids: logger.error("No devices found to track") return False # Get the light IDs from the specified group light_ids = group.get_entity_ids(hass, light_group, light.DOMAIN) if not light_ids: logger.error("No lights found to turn on") return False def calc_time_for_light_when_sunset(): """Calculate the time when to start fading lights in when sun sets. Returns None if no next_setting data available. Async friendly. """ next_setting = get_astral_event_next(hass, 'sunset') if not next_setting: return None return next_setting - LIGHT_TRANSITION_TIME * len(light_ids) def async_turn_on_before_sunset(light_id): """Turn on lights.""" if not device_tracker.is_on(hass) or light.is_on(hass, light_id): return light.async_turn_on(hass, light_id, transition=LIGHT_TRANSITION_TIME.seconds, profile=light_profile) def async_turn_on_factory(light_id): """Generate turn on callbacks as factory.""" @callback def async_turn_on_light(now): """Turn on specific light.""" async_turn_on_before_sunset(light_id) return async_turn_on_light # Track every time sun rises so we can schedule a time-based # pre-sun set event @callback def schedule_light_turn_on(now): """Turn on all the lights at the moment sun sets. We will schedule to have each light start after one another and slowly transition in. """ start_point = calc_time_for_light_when_sunset() if not start_point: return for index, light_id in enumerate(light_ids): async_track_point_in_utc_time( hass, async_turn_on_factory(light_id), start_point + index * LIGHT_TRANSITION_TIME) async_track_point_in_utc_time(hass, schedule_light_turn_on, get_astral_event_next(hass, 'sunrise')) # If the sun is already above horizon schedule the time-based pre-sun set # event. if is_up(hass): schedule_light_turn_on(None) @callback def check_light_on_dev_state_change(entity, old_state, new_state): """Handle tracked device state changes.""" lights_are_on = group.is_on(hass, light_group) light_needed = not (lights_are_on or is_up(hass)) # These variables are needed for the elif check now = dt_util.utcnow() start_point = calc_time_for_light_when_sunset() # Do we need lights? if light_needed: logger.info("Home coming event for %s. Turning lights on", entity) light.async_turn_on(hass, light_ids, profile=light_profile) # Are we in the time span were we would turn on the lights # if someone would be home? # Check this by seeing if current time is later then the point # in time when we would start putting the lights on. elif (start_point and start_point < now < get_astral_event_next(hass, 'sunset')): # Check for every light if it would be on if someone was home # when the fading in started and turn it on if so for index, light_id in enumerate(light_ids): if now > start_point + index * LIGHT_TRANSITION_TIME: light.async_turn_on(hass, light_id) else: # If this light didn't happen to be turned on yet so # will all the following then, break. break async_track_state_change(hass, device_entity_ids, check_light_on_dev_state_change, STATE_NOT_HOME, STATE_HOME) if disable_turn_off: return True @callback def turn_off_lights_when_all_leave(entity, old_state, new_state): """Handle device group state change.""" if not group.is_on(hass, light_group): return logger.info( "Everyone has left but there are lights on. Turning them off") light.async_turn_off(hass, light_ids) async_track_state_change(hass, device_group, turn_off_lights_when_all_leave, STATE_HOME, STATE_NOT_HOME) return True
def test_from_sunset_to_sunrise(self): """Test period from sunset to sunrise.""" test_time = self.hass.config.time_zone.localize(datetime( 2019, 1, 12)).astimezone(pytz.UTC) sunset = dt_util.as_local( get_astral_event_date(self.hass, "sunset", test_time)) sunrise = dt_util.as_local( get_astral_event_next(self.hass, "sunrise", sunset)) # assert sunset == sunrise config = { "binary_sensor": [{ "platform": "tod", "name": "Night", "after": "sunset", "before": "sunrise", }] } entity_id = "binary_sensor.night" testtime = sunset + timedelta(minutes=-1) with patch( "homeassistant.components.tod.binary_sensor.dt_util.utcnow", return_value=testtime, ): setup_component(self.hass, "binary_sensor", config) self.hass.block_till_done() state = self.hass.states.get(entity_id) assert state.state == STATE_OFF testtime = sunset with patch( "homeassistant.components.tod.binary_sensor.dt_util.utcnow", return_value=testtime, ): self.hass.bus.fire(ha.EVENT_TIME_CHANGED, {ha.ATTR_NOW: testtime}) self.hass.block_till_done() state = self.hass.states.get(entity_id) assert state.state == STATE_ON testtime = sunset + timedelta(minutes=1) with patch( "homeassistant.components.tod.binary_sensor.dt_util.utcnow", return_value=testtime, ): self.hass.bus.fire(ha.EVENT_TIME_CHANGED, {ha.ATTR_NOW: testtime}) self.hass.block_till_done() state = self.hass.states.get(entity_id) assert state.state == STATE_ON testtime = sunrise + timedelta(minutes=-1) with patch( "homeassistant.components.tod.binary_sensor.dt_util.utcnow", return_value=testtime, ): self.hass.bus.fire(ha.EVENT_TIME_CHANGED, {ha.ATTR_NOW: testtime}) self.hass.block_till_done() state = self.hass.states.get(entity_id) assert state.state == STATE_ON testtime = sunrise with patch( "homeassistant.components.tod.binary_sensor.dt_util.utcnow", return_value=testtime, ): self.hass.bus.fire(ha.EVENT_TIME_CHANGED, {ha.ATTR_NOW: testtime}) self.hass.block_till_done() state = self.hass.states.get(entity_id) self.hass.block_till_done() # assert state == "dupa" assert state.state == STATE_OFF testtime = sunrise + timedelta(minutes=1) with patch( "homeassistant.components.tod.binary_sensor.dt_util.utcnow", return_value=testtime, ): self.hass.bus.fire(ha.EVENT_TIME_CHANGED, {ha.ATTR_NOW: testtime}) self.hass.block_till_done() state = self.hass.states.get(entity_id) assert state.state == STATE_OFF
async def async_setup(hass, config): """Set up the triggers to control lights based on device presence.""" logger = logging.getLogger(__name__) device_tracker = hass.components.device_tracker group = hass.components.group light = hass.components.light person = hass.components.person conf = config[DOMAIN] disable_turn_off = conf.get(CONF_DISABLE_TURN_OFF) light_group = conf.get(CONF_LIGHT_GROUP) light_profile = conf.get(CONF_LIGHT_PROFILE) device_group = conf.get(CONF_DEVICE_GROUP) if device_group is None: device_entity_ids = hass.states.async_entity_ids(device_tracker.DOMAIN) else: device_entity_ids = group.get_entity_ids(device_group, device_tracker.DOMAIN) device_entity_ids.extend( group.get_entity_ids(device_group, person.DOMAIN)) if not device_entity_ids: logger.error("No devices found to track") return False # Get the light IDs from the specified group if light_group is None: light_ids = hass.states.async_entity_ids(light.DOMAIN) else: light_ids = group.get_entity_ids(light_group, light.DOMAIN) if not light_ids: logger.error("No lights found to turn on") return False @callback def anyone_home(): """Test if anyone is home.""" return any(device_tracker.is_on(dt_id) for dt_id in device_entity_ids) @callback def any_light_on(): """Test if any light on.""" return any(light.is_on(light_id) for light_id in light_ids) def calc_time_for_light_when_sunset(): """Calculate the time when to start fading lights in when sun sets. Returns None if no next_setting data available. Async friendly. """ next_setting = get_astral_event_next(hass, SUN_EVENT_SUNSET) if not next_setting: return None return next_setting - LIGHT_TRANSITION_TIME * len(light_ids) def async_turn_on_before_sunset(light_id): """Turn on lights.""" if not anyone_home() or light.is_on(light_id): return hass.async_create_task( hass.services.async_call( DOMAIN_LIGHT, SERVICE_TURN_ON, { ATTR_ENTITY_ID: light_id, ATTR_TRANSITION: LIGHT_TRANSITION_TIME.seconds, ATTR_PROFILE: light_profile, }, )) def async_turn_on_factory(light_id): """Generate turn on callbacks as factory.""" @callback def async_turn_on_light(now): """Turn on specific light.""" async_turn_on_before_sunset(light_id) return async_turn_on_light # Track every time sun rises so we can schedule a time-based # pre-sun set event @callback def schedule_light_turn_on(now): """Turn on all the lights at the moment sun sets. We will schedule to have each light start after one another and slowly transition in. """ start_point = calc_time_for_light_when_sunset() if not start_point: return for index, light_id in enumerate(light_ids): async_track_point_in_utc_time( hass, async_turn_on_factory(light_id), start_point + index * LIGHT_TRANSITION_TIME, ) async_track_point_in_utc_time( hass, schedule_light_turn_on, get_astral_event_next(hass, SUN_EVENT_SUNRISE)) # If the sun is already above horizon schedule the time-based pre-sun set # event. if is_up(hass): schedule_light_turn_on(None) @callback def check_light_on_dev_state_change(entity, old_state, new_state): """Handle tracked device state changes.""" lights_are_on = any_light_on() light_needed = not (lights_are_on or is_up(hass)) # These variables are needed for the elif check now = dt_util.utcnow() start_point = calc_time_for_light_when_sunset() # Do we need lights? if light_needed: logger.info("Home coming event for %s. Turning lights on", entity) hass.async_create_task( hass.services.async_call( DOMAIN_LIGHT, SERVICE_TURN_ON, { ATTR_ENTITY_ID: light_ids, ATTR_PROFILE: light_profile }, )) # Are we in the time span were we would turn on the lights # if someone would be home? # Check this by seeing if current time is later then the point # in time when we would start putting the lights on. elif start_point and start_point < now < get_astral_event_next( hass, SUN_EVENT_SUNSET): # Check for every light if it would be on if someone was home # when the fading in started and turn it on if so for index, light_id in enumerate(light_ids): if now > start_point + index * LIGHT_TRANSITION_TIME: hass.async_create_task( hass.services.async_call(DOMAIN_LIGHT, SERVICE_TURN_ON, {ATTR_ENTITY_ID: light_id})) else: # If this light didn't happen to be turned on yet so # will all the following then, break. break async_track_state_change( hass, device_entity_ids, check_light_on_dev_state_change, STATE_NOT_HOME, STATE_HOME, ) if disable_turn_off: return True @callback def turn_off_lights_when_all_leave(entity, old_state, new_state): """Handle device group state change.""" # Make sure there is not someone home if anyone_home(): return # Check if any light is on if not any_light_on(): return logger.info( "Everyone has left but there are lights on. Turning them off") hass.async_create_task( hass.services.async_call(DOMAIN_LIGHT, SERVICE_TURN_OFF, {ATTR_ENTITY_ID: light_ids})) async_track_state_change( hass, device_entity_ids, turn_off_lights_when_all_leave, STATE_HOME, STATE_NOT_HOME, ) return True
async def test_norwegian_case_summer(hass): """Test location in Norway where the sun doesn't set in summer.""" hass.config.latitude = 69.6 hass.config.longitude = 18.8 hass.config.elevation = 10.0 test_time = datetime(2010, 6, 1, tzinfo=dt_util.UTC) sunrise = dt_util.as_local( get_astral_event_next(hass, "sunrise", dt_util.as_utc(test_time))) sunset = dt_util.as_local( get_astral_event_next(hass, "sunset", dt_util.as_utc(sunrise))) config = { "binary_sensor": [{ "platform": "tod", "name": "Day", "after": "sunrise", "before": "sunset", }] } entity_id = "binary_sensor.day" testtime = test_time with patch( "homeassistant.components.tod.binary_sensor.dt_util.utcnow", return_value=testtime, ): await async_setup_component(hass, "binary_sensor", config) await hass.async_block_till_done() await hass.async_block_till_done() state = hass.states.get(entity_id) assert state.state == STATE_OFF testtime = sunrise + timedelta(seconds=-1) with patch( "homeassistant.components.tod.binary_sensor.dt_util.utcnow", return_value=testtime, ): hass.bus.async_fire(ha.EVENT_TIME_CHANGED, {ha.ATTR_NOW: testtime}) await hass.async_block_till_done() state = hass.states.get(entity_id) assert state.state == STATE_OFF testtime = sunrise with patch( "homeassistant.components.tod.binary_sensor.dt_util.utcnow", return_value=testtime, ): hass.bus.async_fire(ha.EVENT_TIME_CHANGED, {ha.ATTR_NOW: testtime}) await hass.async_block_till_done() state = hass.states.get(entity_id) assert state.state == STATE_ON testtime = sunrise + timedelta(seconds=1) with patch( "homeassistant.components.tod.binary_sensor.dt_util.utcnow", return_value=testtime, ): hass.bus.async_fire(ha.EVENT_TIME_CHANGED, {ha.ATTR_NOW: testtime}) await hass.async_block_till_done() state = hass.states.get(entity_id) assert state.state == STATE_ON await hass.async_block_till_done() testtime = sunset + timedelta(seconds=-1) with patch( "homeassistant.components.tod.binary_sensor.dt_util.utcnow", return_value=testtime, ): hass.bus.async_fire(ha.EVENT_TIME_CHANGED, {ha.ATTR_NOW: testtime}) await hass.async_block_till_done() state = hass.states.get(entity_id) assert state.state == STATE_ON await hass.async_block_till_done() testtime = sunset with patch( "homeassistant.components.tod.binary_sensor.dt_util.utcnow", return_value=testtime, ): hass.bus.async_fire(ha.EVENT_TIME_CHANGED, {ha.ATTR_NOW: testtime}) await hass.async_block_till_done() state = hass.states.get(entity_id) assert state.state == STATE_OFF await hass.async_block_till_done() testtime = sunset + timedelta(seconds=1) with patch( "homeassistant.components.tod.binary_sensor.dt_util.utcnow", return_value=testtime, ): hass.bus.async_fire(ha.EVENT_TIME_CHANGED, {ha.ATTR_NOW: testtime}) await hass.async_block_till_done() state = hass.states.get(entity_id) assert state.state == STATE_OFF
def test_norwegian_case_summer(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 test_time = self.hass.config.time_zone.localize( datetime(2010, 6, 1)).astimezone(pytz.UTC) sunrise = dt_util.as_local(get_astral_event_next( self.hass, 'sunrise', dt_util.as_utc(test_time))) sunset = dt_util.as_local(get_astral_event_next( self.hass, 'sunset', dt_util.as_utc(test_time))) config = { 'binary_sensor': [ { 'platform': 'tod', 'name': 'Day', 'after': 'sunrise', 'before': 'sunset' } ] } entity_id = 'binary_sensor.day' testtime = test_time with patch('homeassistant.components.tod.binary_sensor.dt_util.utcnow', return_value=testtime): setup_component(self.hass, 'binary_sensor', config) self.hass.block_till_done() state = self.hass.states.get(entity_id) assert state.state == STATE_OFF testtime = sunrise + timedelta(seconds=-1) with patch('homeassistant.components.tod.binary_sensor.dt_util.utcnow', return_value=testtime): self.hass.bus.fire(ha.EVENT_TIME_CHANGED, { ha.ATTR_NOW: testtime}) self.hass.block_till_done() state = self.hass.states.get(entity_id) assert state.state == STATE_OFF testtime = sunrise with patch('homeassistant.components.tod.binary_sensor.dt_util.utcnow', return_value=testtime): self.hass.bus.fire(ha.EVENT_TIME_CHANGED, { ha.ATTR_NOW: testtime}) self.hass.block_till_done() state = self.hass.states.get(entity_id) assert state.state == STATE_ON testtime = sunrise + timedelta(seconds=1) with patch('homeassistant.components.tod.binary_sensor.dt_util.utcnow', return_value=testtime): self.hass.bus.fire(ha.EVENT_TIME_CHANGED, { ha.ATTR_NOW: testtime}) self.hass.block_till_done() state = self.hass.states.get(entity_id) assert state.state == STATE_ON self.hass.block_till_done() testtime = sunset + timedelta(seconds=-1) with patch('homeassistant.components.tod.binary_sensor.dt_util.utcnow', return_value=testtime): self.hass.bus.fire(ha.EVENT_TIME_CHANGED, { ha.ATTR_NOW: testtime}) self.hass.block_till_done() state = self.hass.states.get(entity_id) assert state.state == STATE_ON self.hass.block_till_done() testtime = sunset with patch('homeassistant.components.tod.binary_sensor.dt_util.utcnow', return_value=testtime): self.hass.bus.fire(ha.EVENT_TIME_CHANGED, { ha.ATTR_NOW: testtime}) self.hass.block_till_done() state = self.hass.states.get(entity_id) assert state.state == STATE_OFF self.hass.block_till_done() testtime = sunset + timedelta(seconds=1) with patch('homeassistant.components.tod.binary_sensor.dt_util.utcnow', return_value=testtime): self.hass.bus.fire(ha.EVENT_TIME_CHANGED, { ha.ATTR_NOW: testtime}) self.hass.block_till_done() state = self.hass.states.get(entity_id) assert state.state == STATE_OFF
def test_next_events(hass): """Test retrieving next sun events.""" utc_now = datetime(2016, 11, 1, 8, 0, 0, tzinfo=dt_util.UTC) from astral import LocationInfo import astral.sun utc_today = utc_now.date() location = LocationInfo( latitude=hass.config.latitude, longitude=hass.config.longitude ) mod = -1 while True: next_dawn = astral.sun.dawn( location.observer, date=utc_today + timedelta(days=mod) ) if next_dawn > utc_now: break mod += 1 mod = -1 while True: next_dusk = astral.sun.dusk( location.observer, date=utc_today + timedelta(days=mod) ) if next_dusk > utc_now: break mod += 1 mod = -1 while True: next_midnight = astral.sun.midnight( location.observer, date=utc_today + timedelta(days=mod) ) if next_midnight > utc_now: break mod += 1 mod = -1 while True: next_noon = astral.sun.noon( location.observer, date=utc_today + timedelta(days=mod) ) if next_noon > utc_now: break mod += 1 mod = -1 while True: next_rising = astral.sun.sunrise( location.observer, date=utc_today + timedelta(days=mod) ) if next_rising > utc_now: break mod += 1 mod = -1 while True: next_setting = astral.sun.sunset( location.observer, utc_today + timedelta(days=mod) ) if next_setting > utc_now: break mod += 1 with patch("homeassistant.helpers.condition.dt_util.utcnow", return_value=utc_now): assert next_dawn == sun.get_astral_event_next(hass, "dawn") assert next_dusk == sun.get_astral_event_next(hass, "dusk") assert next_midnight == sun.get_astral_event_next(hass, "midnight") assert next_noon == sun.get_astral_event_next(hass, "noon") assert next_rising == sun.get_astral_event_next(hass, SUN_EVENT_SUNRISE) assert next_setting == sun.get_astral_event_next(hass, SUN_EVENT_SUNSET)