feat: make home assistant add-on run optimization by default (#940)

When running as Home Assistant add-on the only viable usage is running with
cyclic optimization. Make this the default to als propvide a better experience
for first time users. The optimization will start with demo data, which also
helps to configure Akkudoktor-EOS to the personal usage.

The disabling of the automatic energy management is now an explicit mode
"DISABLED".

Signed-off-by: Bobby Noelte <b0661n0e17e@gmail.com>
This commit is contained in:
Bobby Noelte
2026-03-13 15:48:43 +01:00
committed by GitHub
parent 8a9aec6d57
commit b67148c47f
12 changed files with 81 additions and 44 deletions

View File

@@ -23,7 +23,7 @@ from pydantic import Field, computed_field, field_validator
# settings
from akkudoktoreos.adapter.adapter import AdapterCommonSettings
from akkudoktoreos.config.configabc import SettingsBaseModel
from akkudoktoreos.config.configabc import SettingsBaseModel, is_home_assistant_addon
from akkudoktoreos.config.configmigrate import migrate_config_data, migrate_config_file
from akkudoktoreos.core.cachesettings import CacheCommonSettings
from akkudoktoreos.core.coreabc import SingletonMixin
@@ -69,14 +69,6 @@ def get_absolute_path(
return None
def is_home_assistant_addon() -> bool:
"""Detect Home Assistant add-on environment.
Home Assistant sets this environment variable automatically.
"""
return "HASSIO_TOKEN" in os.environ or "SUPERVISOR_TOKEN" in os.environ
def default_data_folder_path() -> Path:
"""Provide default data folder path.

View File

@@ -1,6 +1,7 @@
"""Abstract and base classes for configuration."""
import calendar
import os
from typing import Any, ClassVar, Iterator, Optional, Union
import numpy as np
@@ -18,6 +19,14 @@ from akkudoktoreos.utils.datetimeutil import (
)
def is_home_assistant_addon() -> bool:
"""Detect Home Assistant add-on environment.
Home Assistant sets this environment variable automatically.
"""
return "HASSIO_TOKEN" in os.environ or "SUPERVISOR_TOKEN" in os.environ
class SettingsBaseModel(PydanticBaseModel):
"""Base model class for all settings configurations."""

View File

@@ -154,8 +154,8 @@ class EnergyManagement(
@classmethod
def _run(
cls,
start_datetime: Optional[DateTime] = None,
mode: Optional[EnergyManagementMode] = None,
start_datetime: DateTime,
mode: EnergyManagementMode,
genetic_parameters: Optional[GeneticOptimizationParameters] = None,
genetic_individuals: Optional[int] = None,
genetic_seed: Optional[int] = None,
@@ -170,14 +170,11 @@ class EnergyManagement(
optimization depending on the selected mode or configuration.
Args:
start_datetime (DateTime, optional): The starting timestamp
of the energy management run. Defaults to the current datetime
if not provided.
mode (EnergyManagementMode, optional): The management mode to use. Must be one of:
start_datetime (DateTime): The starting timestamp of the energy management run.
mode (EnergyManagementMode): The management mode to use. Must be one of:
- "OPTIMIZATION": Runs the optimization process.
- "PREDICTION": Updates the forecast without optimization.
Defaults to the mode defined in the current configuration.
- "DISABLED": Does not run.
genetic_parameters (GeneticOptimizationParameters, optional): The
parameter set for the genetic algorithm. If not provided, it will
be constructed based on the current configuration and predictions.
@@ -196,8 +193,10 @@ class EnergyManagement(
None
"""
# Ensure there is only one optimization/ energy management run at a time
if mode not in (None, "PREDICTION", "OPTIMIZATION"):
if not EnergyManagementMode.is_valid(mode):
raise ValueError(f"Unknown energy management mode {mode}.")
if mode == EnergyManagementMode.DISABLED:
return
logger.info("Starting energy management run.")
@@ -220,9 +219,7 @@ class EnergyManagement(
cls._stage = EnergyManagementStage.FORECAST_RETRIEVAL
if mode is None:
mode = cls.config.ems.mode
if mode is None or mode == "PREDICTION":
if mode == EnergyManagementMode.PREDICTION:
# Update the predictions
cls.prediction.update_data(force_enable=force_enable, force_update=force_update)
logger.info("Energy management run done (predictions updated)")
@@ -346,6 +343,10 @@ class EnergyManagement(
async with self._run_lock:
loop = get_running_loop()
# Create a partial function with parameters "baked in"
if start_datetime is None:
start_datetime = to_datetime()
if mode is None:
mode = self.config.ems.mode
func = partial(
EnergyManagement._run,
start_datetime=start_datetime,

View File

@@ -4,19 +4,47 @@ Kept in an extra module to avoid cyclic dependencies on package import.
"""
from enum import Enum
from typing import Optional
from typing import Optional, Union
from pydantic import Field
from akkudoktoreos.config.configabc import SettingsBaseModel
from akkudoktoreos.config.configabc import SettingsBaseModel, is_home_assistant_addon
class EnergyManagementMode(str, Enum):
"""Energy management mode."""
DISABLED = "DISABLED"
PREDICTION = "PREDICTION"
OPTIMIZATION = "OPTIMIZATION"
@classmethod
def is_valid(cls, mode: Union[str, "EnergyManagementMode"]) -> bool:
"""Check if value is a valid mode."""
try:
cls(mode)
return True
except (ValueError, TypeError):
return False
@classmethod
def from_value(cls, value: str) -> Optional["EnergyManagementMode"]:
"""Safely convert string to enum, return None if invalid."""
try:
return cls(value)
except ValueError:
return None
def ems_default_mode() -> EnergyManagementMode:
"""Provide default EMS mode.
Returns OPTIMIZATION when running under Home Assistant, else DISABLED.
"""
if is_home_assistant_addon():
return EnergyManagementMode.OPTIMIZATION
return EnergyManagementMode.DISABLED
class EnergyManagementCommonSettings(SettingsBaseModel):
"""Energy Management Configuration."""
@@ -38,10 +66,10 @@ class EnergyManagementCommonSettings(SettingsBaseModel):
},
)
mode: Optional[EnergyManagementMode] = Field(
default=None,
mode: EnergyManagementMode = Field(
default_factory=ems_default_mode,
json_schema_extra={
"description": "Energy management mode [OPTIMIZATION | PREDICTION].",
"description": "Energy management mode [DISABLED | OPTIMIZATION | PREDICTION].",
"examples": ["OPTIMIZATION", "PREDICTION"],
},
)

View File

@@ -139,7 +139,7 @@ class HomeApplianceParameters(DeviceParameters):
"description": "List of allowed time windows. Defaults to optimization general time window.",
"examples": [
[
{"start_time": "10:00", "duration": "2 hours"},
{"start_time": "10:00", "duration": "3 hours"},
],
],
},

View File

@@ -727,9 +727,9 @@ def Configuration(
# Home Assistant adapter optimization solution entities
update_form_factory = make_config_update_list_form(eos_solution_entity_ids)
elif config["name"].startswith("ems.mode"):
# Energy managemnt mode
# Energy management mode
update_form_factory = make_config_update_value_form(
["OPTIMIZATION", "PREDICTION", "None"]
["OPTIMIZATION", "PREDICTION", "DISABLED"]
)
elif config["name"].endswith("elecpricefixed.time_windows.windows"):
update_form_factory = make_config_update_time_windows_windows_form(