"""Home Assistant scheduler commander adapter. This adapter implements ISchedulerCommander by calling Home Assistant scheduler services to trigger heating actions. """ from __future__ import annotations import logging from datetime import datetime from homeassistant.core import HomeAssistant from ...domain.interfaces.scheduler_commander_interface import ISchedulerCommander from .utils import get_entity_name _LOGGER = logging.getLogger(__name__) # Scheduler service configuration SCHEDULER_DOMAIN = "scheduler" SERVICE_RUN_ACTION = "run_action" class HASchedulerCommander(ISchedulerCommander): """Home Assistant implementation of scheduler commander. Executes scheduler commands using the scheduler-component's run_action service. See: https://github.com/nielsfaber/scheduler-component/#schedulerrun_action This adapter contains NO business logic - it only translates domain requests into Home Assistant service calls. """ def __init__(self, hass: HomeAssistant) -> None: """Initialize the scheduler commander adapter. Args: hass: Home Assistant instance scheduler_entity_id: The scheduler entity ID to control """ self._hass = hass async def run_action(self, target_time: datetime, scheduler_entity_id: str) -> None: """Trigger a scheduler action for a specific timeslot. This will start heating in the mode configured in the scheduler for the timeslot at the given time. Args: target_time: The time of the scheduler timeslot to trigger scheduler_entity_id: Scheduler entity ID to control Raises: ValueError: If scheduler entity is not configured """ if not scheduler_entity_id: _LOGGER.error("Cannot run action: no scheduler entity configured") raise ValueError("Scheduler entity ID not configured") # Format time as HH:MM for scheduler service trigger_time_str = target_time.strftime("%H:%M") device_name = get_entity_name(self._hass, scheduler_entity_id) _LOGGER.info("[%s] Triggering scheduler action at time %s", device_name, trigger_time_str) try: await self._hass.services.async_call( SCHEDULER_DOMAIN, SERVICE_RUN_ACTION, { "entity_id": scheduler_entity_id, "time": trigger_time_str, "skip_conditions": False, # Respect scheduler conditions }, blocking=True, ) _LOGGER.debug("Scheduler action triggered successfully") except Exception as err: _LOGGER.error( "Failed to trigger scheduler action for %s: %s", scheduler_entity_id, err, exc_info=True, ) raise async def cancel_action(self, scheduler_entity_id: str) -> None: """Cancel current scheduler action and return to current timeslot. This is used to stop overshoot by reverting to the mode configured for the current time (now()). Note: The scheduler-component doesn't have a direct "cancel" service. This implementation triggers the action for "now" which effectively reverts to the current scheduled state. Args: scheduler_entity_id: Scheduler entity ID to control Raises: ValueError: If scheduler entity is not configured """ if not scheduler_entity_id: _LOGGER.error("Cannot cancel action: no scheduler entity configured") raise ValueError("Scheduler entity ID not configured") # Get current time from homeassistant.util import dt as dt_util now = dt_util.now() current_time_str = now.strftime("%H:%M") device_name = get_entity_name(self._hass, scheduler_entity_id) _LOGGER.info( "[%s] Canceling scheduler action by reverting to current time %s", device_name, current_time_str, ) try: await self._hass.services.async_call( SCHEDULER_DOMAIN, SERVICE_RUN_ACTION, { "entity_id": scheduler_entity_id, "time": current_time_str, "skip_conditions": False, }, blocking=True, ) _LOGGER.debug("Scheduler action canceled successfully") except Exception as err: _LOGGER.error( "Failed to cancel scheduler action for %s: %s", scheduler_entity_id, err, exc_info=True, ) raise