97 lines
3.3 KiB
Python
97 lines
3.3 KiB
Python
"""Heating pilot - the aggregate root for heating decisions."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import logging
|
|
|
|
from ..interfaces import IDecisionStrategy, ISchedulerCommander
|
|
from ..value_objects import (
|
|
EnvironmentState,
|
|
HeatingDecision,
|
|
)
|
|
|
|
_LOGGER = logging.getLogger(__name__)
|
|
|
|
|
|
class HeatingPilot:
|
|
"""Coordinates heating decisions for a single VTherm.
|
|
|
|
This is the aggregate root that orchestrates all domain logic
|
|
for intelligent heating control. It delegates decision-making to
|
|
a configurable strategy, allowing users to choose between:
|
|
|
|
- Simple rule-based decisions (no ML required)
|
|
- ML-powered decisions (requires IHP-ML-Models add-on)
|
|
|
|
This design follows the Strategy pattern, making the pilot
|
|
independent of the decision algorithm complexity.
|
|
|
|
Attributes:
|
|
_decision_strategy: Strategy for making heating decisions
|
|
_scheduler_commander: Interface to control scheduler actions
|
|
"""
|
|
|
|
def __init__(
|
|
self,
|
|
decision_strategy: IDecisionStrategy,
|
|
scheduler_commander: ISchedulerCommander,
|
|
) -> None:
|
|
"""Initialize the heating pilot.
|
|
|
|
Args:
|
|
decision_strategy: Strategy for making heating decisions
|
|
(simple rules or ML-based)
|
|
scheduler_commander: Implementation of scheduler control interface
|
|
"""
|
|
_LOGGER.debug("Initializing HeatingPilot")
|
|
self._decision_strategy = decision_strategy
|
|
self._scheduler_commander = scheduler_commander
|
|
_LOGGER.info(f"HeatingPilot initialized with strategy: {type(decision_strategy).__name__}")
|
|
|
|
async def decide_heating_action(
|
|
self,
|
|
environment: EnvironmentState,
|
|
) -> HeatingDecision:
|
|
"""Decide what heating action to take based on current conditions.
|
|
|
|
This method delegates the decision to the configured strategy,
|
|
which can be either simple rule-based or ML-powered.
|
|
|
|
Args:
|
|
environment: Current environmental conditions
|
|
|
|
Returns:
|
|
A heating decision with the action to take
|
|
"""
|
|
_LOGGER.debug("HeatingPilot.decide_heating_action called")
|
|
_LOGGER.debug(f"Delegating decision to {type(self._decision_strategy).__name__}")
|
|
|
|
decision = await self._decision_strategy.decide_heating_action(environment)
|
|
|
|
_LOGGER.info(f"HeatingPilot decision: {decision.action.value}")
|
|
return decision
|
|
|
|
async def check_overshoot_risk(
|
|
self,
|
|
environment: EnvironmentState,
|
|
current_slope: float,
|
|
) -> HeatingDecision:
|
|
"""Check if heating should stop to prevent overshooting target.
|
|
|
|
This method delegates the overshoot check to the configured strategy.
|
|
|
|
Args:
|
|
environment: Current environmental conditions
|
|
current_slope: Current heating rate in °C/hour
|
|
|
|
Returns:
|
|
Decision to stop heating if overshoot is detected
|
|
"""
|
|
_LOGGER.debug("HeatingPilot.check_overshoot_risk called")
|
|
_LOGGER.debug(f"Delegating overshoot check to {type(self._decision_strategy).__name__}")
|
|
|
|
decision = await self._decision_strategy.check_overshoot_risk(environment, current_slope)
|
|
|
|
_LOGGER.info(f"HeatingPilot overshoot check: {decision.action.value}")
|
|
return decision
|