New apps Added
This commit is contained in:
@@ -0,0 +1,185 @@
|
||||
"""Heating Orchestrator.
|
||||
|
||||
The orchestrator composes use cases to implement complex workflows.
|
||||
It coordinates multiple use cases but contains NO business logic itself.
|
||||
|
||||
This is the main entry point for heating operations from the infrastructure layer.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from .use_cases import (
|
||||
CalculateAnticipationUseCase,
|
||||
CheckOvershootRiskUseCase,
|
||||
ControlPreheatingUseCase,
|
||||
ScheduleAnticipationActionUseCase,
|
||||
UpdateCacheDataUseCase,
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from datetime import datetime
|
||||
from typing import Any
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class HeatingOrchestrator:
|
||||
"""Orchestrates heating use cases.
|
||||
|
||||
The orchestrator is responsible for:
|
||||
1. Composing use cases to implement workflows
|
||||
2. Coordinating the execution order of use cases
|
||||
3. Managing cross-use-case dependencies
|
||||
|
||||
NO business logic - pure composition and coordination.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
calculate_anticipation: CalculateAnticipationUseCase,
|
||||
control_preheating: ControlPreheatingUseCase,
|
||||
schedule_anticipation_action: ScheduleAnticipationActionUseCase,
|
||||
check_overshoot_risk: CheckOvershootRiskUseCase,
|
||||
update_cache: UpdateCacheDataUseCase,
|
||||
) -> None:
|
||||
"""Initialize the orchestrator with use cases.
|
||||
|
||||
Args:
|
||||
calculate_anticipation: Use case for calculating anticipation data
|
||||
control_preheating: Use case for controlling preheating
|
||||
schedule_anticipation_action: Use case for scheduling anticipation actions
|
||||
check_overshoot_risk: Use case for checking overshoot risk
|
||||
update_cache: Use case for updating cache
|
||||
"""
|
||||
_LOGGER.debug("Initializing HeatingOrchestrator")
|
||||
self._calculate_anticipation = calculate_anticipation
|
||||
self._control_preheating = control_preheating
|
||||
self._schedule_anticipation_action = schedule_anticipation_action
|
||||
self._check_overshoot_risk = check_overshoot_risk
|
||||
self._update_cache = update_cache
|
||||
|
||||
async def calculate_anticipation_only(
|
||||
self,
|
||||
target_time: datetime | None = None,
|
||||
target_temp: float | None = None,
|
||||
) -> dict:
|
||||
"""Calculate anticipation data only (no scheduling).
|
||||
|
||||
For users who use the service without a scheduler (via REST API).
|
||||
|
||||
Args:
|
||||
target_time: Target time for heating
|
||||
target_temp: Target temperature
|
||||
|
||||
Returns:
|
||||
Dict with anticipation data
|
||||
"""
|
||||
_LOGGER.debug("Entering HeatingOrchestrator.calculate_anticipation_only()")
|
||||
|
||||
result = await self._calculate_anticipation.calculate_anticipation_datas(
|
||||
target_time=target_time,
|
||||
target_temp=target_temp,
|
||||
)
|
||||
|
||||
_LOGGER.debug("Exiting calculate_anticipation_only() -> data")
|
||||
return result
|
||||
|
||||
async def disable_preheating(self, scheduler_entity_id: str) -> None:
|
||||
"""Disable preheating (cancel timer and active preheating).
|
||||
|
||||
Args:
|
||||
scheduler_entity_id: Scheduler entity to cancel
|
||||
"""
|
||||
_LOGGER.debug("Entering HeatingOrchestrator.disable_preheating()")
|
||||
|
||||
# Delegate to schedule_anticipation_action to cancel timer
|
||||
await self._schedule_anticipation_action.cancel_action()
|
||||
|
||||
# Cancel active preheating if any
|
||||
await self._control_preheating.cancel_preheating(scheduler_entity_id)
|
||||
|
||||
_LOGGER.info("Preheating disabled")
|
||||
_LOGGER.debug("Exiting HeatingOrchestrator.disable_preheating()")
|
||||
|
||||
async def reset_all_learning_data(self, device_id: str) -> None:
|
||||
"""Reset all learned data (LHS + cycles).
|
||||
|
||||
Args:
|
||||
device_id: Device identifier
|
||||
"""
|
||||
_LOGGER.debug("Entering HeatingOrchestrator.reset_all_learning_data()")
|
||||
|
||||
await self._update_cache.reset_cache(device_id)
|
||||
|
||||
_LOGGER.debug("Exiting HeatingOrchestrator.reset_all_learning_data()")
|
||||
|
||||
def is_preheating_active(self) -> bool:
|
||||
"""Check if preheating is currently active.
|
||||
|
||||
Returns:
|
||||
True if preheating is active, False otherwise
|
||||
"""
|
||||
return self._control_preheating.is_preheating_active()
|
||||
|
||||
async def calculate_and_schedule_anticipation(self, ihp_enabled: bool = True) -> dict[str, Any]:
|
||||
"""Calculate anticipation and handle scheduling based on IHP status.
|
||||
|
||||
This orchestrator method:
|
||||
1. Calculates anticipation data
|
||||
2. Delegates scheduling decisions to ScheduleAnticipationActionUseCase
|
||||
3. Always returns anticipation data structure
|
||||
|
||||
Args:
|
||||
ihp_enabled: Whether IHP preheating is enabled
|
||||
|
||||
Returns:
|
||||
Dict with anticipation data for sensors (always returns structure)
|
||||
"""
|
||||
_LOGGER.debug(
|
||||
"Entering HeatingOrchestrator.calculate_and_schedule_anticipation(ihp_enabled=%s)",
|
||||
ihp_enabled,
|
||||
)
|
||||
|
||||
# Step 1: Always calculate anticipation data regardless of IHP state.
|
||||
# The switch only controls whether preheating is scheduled, not whether
|
||||
# calculations happen. This ensures sensors and LHS caches stay up-to-date.
|
||||
anticipation_data = await self._calculate_anticipation.calculate_anticipation_datas()
|
||||
|
||||
# Step 2: Delegate scheduling decisions to use case (respects ihp_enabled)
|
||||
await self._schedule_anticipation_action.handle_anticipation_scheduling(
|
||||
anticipation_data,
|
||||
ihp_enabled,
|
||||
)
|
||||
|
||||
_LOGGER.debug("Exiting HeatingOrchestrator.calculate_and_schedule_anticipation() -> data")
|
||||
return anticipation_data
|
||||
|
||||
async def check_and_prevent_overshoot(self, scheduler_entity_id: str) -> bool:
|
||||
"""Check overshoot risk and cancel preheating if needed.
|
||||
|
||||
Args:
|
||||
scheduler_entity_id: Scheduler entity to cancel if overshoot detected
|
||||
|
||||
Returns:
|
||||
True if overshoot detected and cancellation triggered, False otherwise
|
||||
"""
|
||||
_LOGGER.debug(
|
||||
"Entering HeatingOrchestrator.check_and_prevent_overshoot(scheduler=%s)",
|
||||
scheduler_entity_id,
|
||||
)
|
||||
|
||||
overshoot_detected = await self._check_overshoot_risk.check_and_prevent_overshoot(
|
||||
scheduler_entity_id=scheduler_entity_id
|
||||
)
|
||||
|
||||
if overshoot_detected:
|
||||
await self._schedule_anticipation_action.cancel_action()
|
||||
|
||||
_LOGGER.debug(
|
||||
"Exiting HeatingOrchestrator.check_and_prevent_overshoot() -> %s",
|
||||
overshoot_detected,
|
||||
)
|
||||
return overshoot_detected
|
||||
Reference in New Issue
Block a user