124 lines
4.0 KiB
Python
124 lines
4.0 KiB
Python
"""Offer time listening automation rules."""
|
|
from datetime import datetime
|
|
import logging
|
|
|
|
import voluptuous as vol
|
|
|
|
from homeassistant.const import CONF_AT, CONF_PLATFORM
|
|
from homeassistant.core import callback
|
|
from homeassistant.helpers import config_validation as cv
|
|
from homeassistant.helpers.event import (
|
|
async_track_point_in_time,
|
|
async_track_state_change,
|
|
async_track_time_change,
|
|
)
|
|
import homeassistant.util.dt as dt_util
|
|
|
|
# mypy: allow-untyped-defs, no-check-untyped-defs
|
|
|
|
_LOGGER = logging.getLogger(__name__)
|
|
|
|
_TIME_TRIGGER_SCHEMA = vol.Any(
|
|
cv.time,
|
|
vol.All(str, cv.entity_domain("input_datetime")),
|
|
msg="Expected HH:MM, HH:MM:SS or Entity ID from domain 'input_datetime'",
|
|
)
|
|
|
|
TRIGGER_SCHEMA = vol.Schema(
|
|
{
|
|
vol.Required(CONF_PLATFORM): "time",
|
|
vol.Required(CONF_AT): vol.All(cv.ensure_list, [_TIME_TRIGGER_SCHEMA]),
|
|
}
|
|
)
|
|
|
|
|
|
async def async_attach_trigger(hass, config, action, automation_info):
|
|
"""Listen for state changes based on configuration."""
|
|
entities = {}
|
|
removes = []
|
|
|
|
@callback
|
|
def time_automation_listener(now):
|
|
"""Listen for time changes and calls action."""
|
|
hass.async_run_job(action, {"trigger": {"platform": "time", "now": now}})
|
|
|
|
@callback
|
|
def update_entity_trigger(entity_id, old_state=None, new_state=None):
|
|
# If a listener was already set up for entity, remove it.
|
|
remove = entities.get(entity_id)
|
|
if remove:
|
|
remove()
|
|
removes.remove(remove)
|
|
remove = None
|
|
|
|
# Check state of entity. If valid, set up a listener.
|
|
if new_state:
|
|
has_date = new_state.attributes["has_date"]
|
|
if has_date:
|
|
year = new_state.attributes["year"]
|
|
month = new_state.attributes["month"]
|
|
day = new_state.attributes["day"]
|
|
has_time = new_state.attributes["has_time"]
|
|
if has_time:
|
|
hour = new_state.attributes["hour"]
|
|
minute = new_state.attributes["minute"]
|
|
second = new_state.attributes["second"]
|
|
else:
|
|
# If no time then use midnight.
|
|
hour = minute = second = 0
|
|
|
|
if has_date:
|
|
# If input_datetime has date, then track point in time.
|
|
trigger_dt = dt_util.DEFAULT_TIME_ZONE.localize(
|
|
datetime(year, month, day, hour, minute, second)
|
|
)
|
|
# Only set up listener if time is now or in the future.
|
|
if trigger_dt >= dt_util.now():
|
|
remove = async_track_point_in_time(
|
|
hass, time_automation_listener, trigger_dt
|
|
)
|
|
elif has_time:
|
|
# Else if it has time, then track time change.
|
|
remove = async_track_time_change(
|
|
hass,
|
|
time_automation_listener,
|
|
hour=hour,
|
|
minute=minute,
|
|
second=second,
|
|
)
|
|
|
|
# Was a listener set up?
|
|
if remove:
|
|
removes.append(remove)
|
|
|
|
entities[entity_id] = remove
|
|
|
|
for at_time in config[CONF_AT]:
|
|
if isinstance(at_time, str):
|
|
# input_datetime entity
|
|
update_entity_trigger(at_time, new_state=hass.states.get(at_time))
|
|
else:
|
|
# datetime.time
|
|
removes.append(
|
|
async_track_time_change(
|
|
hass,
|
|
time_automation_listener,
|
|
hour=at_time.hour,
|
|
minute=at_time.minute,
|
|
second=at_time.second,
|
|
)
|
|
)
|
|
|
|
# Track state changes of any entities.
|
|
removes.append(
|
|
async_track_state_change(hass, list(entities), update_entity_trigger)
|
|
)
|
|
|
|
@callback
|
|
def remove_track_time_changes():
|
|
"""Remove tracked time changes."""
|
|
for remove in removes:
|
|
remove()
|
|
|
|
return remove_track_time_changes
|