1 """Support to turn on lights based on the states."""
3 from datetime
import timedelta
4 from functools
import partial
7 import voluptuous
as vol
10 DOMAIN
as DOMAIN_DEVICE_TRACKER,
11 is_on
as device_tracker_is_on,
17 DOMAIN
as DOMAIN_LIGHT,
23 EVENT_HOMEASSISTANT_START,
34 async_track_point_in_utc_time,
35 async_track_state_change_event,
41 DOMAIN =
"device_sun_light_trigger"
42 CONF_DEVICE_GROUP =
"device_group"
43 CONF_DISABLE_TURN_OFF =
"disable_turn_off"
44 CONF_LIGHT_GROUP =
"light_group"
45 CONF_LIGHT_PROFILE =
"light_profile"
47 DEFAULT_DISABLE_TURN_OFF =
False
48 DEFAULT_LIGHT_PROFILE =
"relax"
52 CONFIG_SCHEMA = vol.Schema(
56 vol.Optional(CONF_DEVICE_GROUP): cv.entity_id,
58 CONF_DISABLE_TURN_OFF, default=DEFAULT_DISABLE_TURN_OFF
60 vol.Optional(CONF_LIGHT_GROUP): cv.string,
62 CONF_LIGHT_PROFILE, default=DEFAULT_LIGHT_PROFILE
67 extra=vol.ALLOW_EXTRA,
71 async
def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
72 """Set up the triggers to control lights based on device presence."""
74 disable_turn_off = conf[CONF_DISABLE_TURN_OFF]
75 light_group = conf.get(CONF_LIGHT_GROUP)
76 light_profile = conf[CONF_LIGHT_PROFILE]
77 device_group = conf.get(CONF_DEVICE_GROUP)
79 async
def activate_on_start(_):
80 """Activate automation."""
82 hass, device_group, light_group, light_profile, disable_turn_off
86 await activate_on_start(
None)
88 hass.bus.async_listen_once(EVENT_HOMEASSISTANT_START, activate_on_start)
94 hass, device_group, light_group, light_profile, disable_turn_off
96 """Activate the automation."""
97 logger = logging.getLogger(__name__)
99 if device_group
is None:
100 device_entity_ids = hass.states.async_entity_ids(DOMAIN_DEVICE_TRACKER)
102 device_entity_ids = group_get_entity_ids(
103 hass, device_group, DOMAIN_DEVICE_TRACKER
105 device_entity_ids.extend(
106 group_get_entity_ids(hass, device_group, DOMAIN_PERSON)
109 if not device_entity_ids:
110 logger.error(
"No devices found to track")
114 if light_group
is None:
115 light_ids = hass.states.async_entity_ids(DOMAIN_LIGHT)
117 light_ids = group_get_entity_ids(hass, light_group, DOMAIN_LIGHT)
120 logger.error(
"No lights found to turn on")
125 """Test if anyone is home."""
126 return any(device_tracker_is_on(hass, dt_id)
for dt_id
in device_entity_ids)
130 """Test if any light on."""
131 return any(light_is_on(hass, light_id)
for light_id
in light_ids)
133 def calc_time_for_light_when_sunset():
134 """Calculate the time when to start fading lights in when sun sets.
136 Returns None if no next_setting data available.
143 return next_setting - LIGHT_TRANSITION_TIME * len(light_ids)
145 async
def async_turn_on_before_sunset(light_id):
146 """Turn on lights."""
147 if not anyone_home()
or light_is_on(hass, light_id):
149 await hass.services.async_call(
153 ATTR_ENTITY_ID: light_id,
154 ATTR_TRANSITION: LIGHT_TRANSITION_TIME.total_seconds(),
155 ATTR_PROFILE: light_profile,
160 def async_turn_on_factory(light_id):
161 """Generate turn on callbacks as factory."""
163 async
def async_turn_on_light(now):
164 """Turn on specific light."""
165 await async_turn_on_before_sunset(light_id)
167 return async_turn_on_light
172 def schedule_light_turn_on(now):
173 """Turn on all the lights at the moment sun sets.
175 We will schedule to have each light start after one another
176 and slowly transition in.
178 start_point = calc_time_for_light_when_sunset()
182 for index, light_id
in enumerate(light_ids):
185 async_turn_on_factory(light_id),
186 start_point + index * LIGHT_TRANSITION_TIME,
196 schedule_light_turn_on(
None)
199 def check_light_on_dev_state_change(
200 from_state: str, to_state: str, event: Event[EventStateChangedData]
202 """Handle tracked device state changes."""
203 event_data = event.data
205 (old_state := event_data[
"old_state"])
is None
206 or (new_state := event_data[
"new_state"])
is None
207 or old_state.state != from_state
208 or new_state.state != to_state
212 entity = event_data[
"entity_id"]
213 lights_are_on = any_light_on()
214 light_needed =
not (lights_are_on
or is_up(hass))
217 now = dt_util.utcnow()
218 start_point = calc_time_for_light_when_sunset()
222 logger.info(
"Home coming event for %s. Turning lights on", entity)
223 hass.async_create_task(
224 hass.services.async_call(
227 {ATTR_ENTITY_ID: light_ids, ATTR_PROFILE: light_profile},
236 hass, SUN_EVENT_SUNSET
240 for index, light_id
in enumerate(light_ids):
241 if now > start_point + index * LIGHT_TRANSITION_TIME:
242 hass.async_create_task(
243 hass.services.async_call(
244 DOMAIN_LIGHT, SERVICE_TURN_ON, {ATTR_ENTITY_ID: light_id}
256 partial(check_light_on_dev_state_change, STATE_NOT_HOME, STATE_HOME),
263 def turn_off_lights_when_all_leave(entity, old_state, new_state):
264 """Handle device group state change."""
270 if not any_light_on():
273 logger.info(
"Everyone has left but there are lights on. Turning them off")
274 hass.async_create_task(
275 hass.services.async_call(
276 DOMAIN_LIGHT, SERVICE_TURN_OFF, {ATTR_ENTITY_ID: light_ids}
283 partial(turn_off_lights_when_all_leave, STATE_HOME, STATE_NOT_HOME),
def activate_automation(hass, device_group, light_group, light_profile, disable_turn_off)
bool async_setup(HomeAssistant hass, ConfigType config)
CALLBACK_TYPE async_track_state_change_event(HomeAssistant hass, str|Iterable[str] entity_ids, Callable[[Event[EventStateChangedData]], Any] action, HassJobType|None job_type=None)
CALLBACK_TYPE async_track_point_in_utc_time(HomeAssistant hass, HassJob[[datetime], Coroutine[Any, Any, None]|None]|Callable[[datetime], Coroutine[Any, Any, None]|None] action, datetime point_in_time)
bool is_up(HomeAssistant hass, datetime.datetime|None utc_point_in_time=None)
datetime.datetime get_astral_event_next(HomeAssistant hass, str event, datetime.datetime|None utc_point_in_time=None, datetime.timedelta|None offset=None)