fix: device id in solution follows configuration (#964)
Some checks are pending
Bump Version / Bump Version Workflow (push) Waiting to run
CodeQL Advanced / Analyze (actions) (push) Waiting to run
CodeQL Advanced / Analyze (python) (push) Waiting to run
docker-build / platform-excludes (push) Waiting to run
docker-build / build (push) Blocked by required conditions
docker-build / merge (push) Blocked by required conditions
pre-commit / pre-commit (push) Waiting to run
Run Pytest on Pull Request / test (push) Waiting to run

Make device id in solution follow actual configuration.

Adapt version update to new CI bump workflow design.

Signed-off-by: Bobby Noelte <b0661n0e17e@gmail.com>
This commit is contained in:
Bobby Noelte
2026-03-18 08:41:28 +01:00
committed by GitHub
parent ff698baef7
commit 5fb5234e7d
8 changed files with 70 additions and 40 deletions

2
.env
View File

@@ -11,7 +11,7 @@ DOCKER_COMPOSE_DATA_DIR=${HOME}/.local/share/net.akkudoktor.eos
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
# Image / build # Image / build
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
VERSION=config.yaml VERSION=0.3.0.dev2603180681250771
PYTHON_VERSION=3.13.9 PYTHON_VERSION=3.13.9
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------

View File

@@ -178,18 +178,15 @@ docker-build:
@docker compose build @docker compose build
# Propagete version info to all version files # Propagete version info to all version files
# Take UPDATE_FILES from GitHub action bump-version.yml
UPDATE_FILES := $(shell sed -n 's/^[[:space:]]*UPDATE_FILES[[:space:]]*=[[:space:]]*"\([^"]*\)".*/\1/p' \
.github/workflows/bump-version.yml)
prepare-version: install prepare-version: install
@echo "Update version to $(VERSION) from version.py in files $(UPDATE_FILES) and doc" $(PYTHON) ./scripts/update_version.py $(VERSION)
$(PYTHON) ./scripts/update_version.py $(VERSION) $(UPDATE_FILES)
$(PYTHON) ./scripts/convert_lightweight_tags.py $(PYTHON) ./scripts/convert_lightweight_tags.py
$(PYTHON) ./scripts/generate_config_md.py --output-file docs/_generated/config.md $(PYTHON) ./scripts/generate_config_md.py --output-file docs/_generated/config.md
$(PYTHON) ./scripts/generate_openapi_md.py --output-file docs/_generated/openapi.md $(PYTHON) ./scripts/generate_openapi_md.py --output-file docs/_generated/openapi.md
$(PYTHON) ./scripts/generate_openapi.py --output-file openapi.json $(PYTHON) ./scripts/generate_openapi.py --output-file openapi.json
$(PYTEST) -vv --finalize tests/test_doc.py $(PYTEST) -vv --finalize tests/test_doc.py
$(PRECOMMIT) run --all-files $(PRECOMMIT) run --all-files
@echo "Updated version to $(VERSION) from version.py in config files and doc"
test-version: test-version:
echo "Test version information to be correctly set in all version files" echo "Test version information to be correctly set in all version files"

View File

@@ -6,7 +6,7 @@
# the root directory (no add-on folder as usual). # the root directory (no add-on folder as usual).
name: "Akkudoktor-EOS" name: "Akkudoktor-EOS"
version: "0.3.0" version: "0.3.0.dev2603180681250771"
slug: "eos" slug: "eos"
description: "Akkudoktor-EOS add-on" description: "Akkudoktor-EOS add-on"
url: "https://github.com/Akkudoktor-EOS/EOS" url: "https://github.com/Akkudoktor-EOS/EOS"

View File

@@ -1,6 +1,6 @@
# Akkudoktor-EOS # Akkudoktor-EOS
**Version**: `v0.3.0` **Version**: `v0.3.0.dev2603180681250771`
<!-- pyml disable line-length --> <!-- pyml disable line-length -->
**Description**: This project provides a comprehensive solution for simulating and optimizing an energy system based on renewable energy sources. With a focus on photovoltaic (PV) systems, battery storage (batteries), load management (consumer requirements), heat pumps, electric vehicles, and consideration of electricity price data, this system enables forecasting and optimization of energy flow and costs over a specified period. **Description**: This project provides a comprehensive solution for simulating and optimizing an energy system based on renewable energy sources. With a focus on photovoltaic (PV) systems, battery storage (batteries), load management (consumer requirements), heat pumps, electric vehicles, and consideration of electricity price data, this system enables forecasting and optimization of energy flow and costs over a specified period.

View File

@@ -8,7 +8,7 @@
"name": "Apache 2.0", "name": "Apache 2.0",
"url": "https://www.apache.org/licenses/LICENSE-2.0.html" "url": "https://www.apache.org/licenses/LICENSE-2.0.html"
}, },
"version": "v0.3.0" "version": "v0.3.0.dev2603180681250771"
}, },
"paths": { "paths": {
"/v1/admin/cache/clear": { "/v1/admin/cache/clear": {

View File

@@ -19,6 +19,11 @@ PACKAGE_DIR = PROJECT_ROOT / "src" / "akkudoktoreos"
SRC_DIR = PROJECT_ROOT / "src" SRC_DIR = PROJECT_ROOT / "src"
sys.path.insert(0, str(SRC_DIR)) sys.path.insert(0, str(SRC_DIR))
DEFAULT_VERSION_FILES = [
PROJECT_ROOT / ".env", # Docker compose default environment
PROJECT_ROOT / "config.yaml", # Home Assistant config
]
# --- Patterns to match version strings --- # --- Patterns to match version strings ---
VERSION_PATTERNS = [ VERSION_PATTERNS = [
@@ -146,10 +151,13 @@ def main(version: str, files: List[str]):
if __name__ == "__main__": if __name__ == "__main__":
if len(sys.argv) < 3: if len(sys.argv) < 2:
print("Usage: python update_version.py <version> <file1> [file2 ...]") print("Usage: python update_version.py <version> [<file1> [file2 ...]]")
sys.exit(1) sys.exit(1)
version_arg = sys.argv[1] version_arg = sys.argv[1]
files_arg = sys.argv[2:] if len(sys.argv) == 2:
files_arg = [str(f) for f in DEFAULT_VERSION_FILES]
else:
files_arg = sys.argv[2:]
main(version_arg, files_arg) main(version_arg, files_arg)

View File

@@ -23,7 +23,7 @@ from typing import Optional
# For development add `.dev` to previous release # For development add `.dev` to previous release
# For release omit `.dev`. # For release omit `.dev`.
VERSION_BASE = "0.3.0" VERSION_BASE = "0.3.0.dev"
# Project hash of relevant files # Project hash of relevant files
HASH_EOS = "" HASH_EOS = ""

View File

@@ -200,6 +200,27 @@ class GeneticSolution(ConfigMixin, GeneticParametersBaseModel):
return ElectricVehicleResult(**field.to_dict()) return ElectricVehicleResult(**field.to_dict())
return field return field
def _battery_device_id(self) -> str:
"""Get battery device id."""
try:
return self.config.devices.batteries[0].device_id
except Exception:
return "battery1"
def _ev_device_id(self) -> str:
"""Get electric vehicle device id."""
try:
return self.config.devices.electric_vehicles[0].device_id
except Exception:
return "ev1"
def _homeappliance_device_id(self) -> str:
"""Get home appliance device id."""
try:
return self.config.devices.home_appliances[0].device_id
except Exception:
return "homeappliance1"
def _battery_operation_from_solution( def _battery_operation_from_solution(
self, self,
ac_charge: float, ac_charge: float,
@@ -377,7 +398,8 @@ class GeneticSolution(ConfigMixin, GeneticParametersBaseModel):
) )
# Add battery data # Add battery data
solution["battery1_soc_factor"] = [ battery_device_id = self._battery_device_id()
solution[f"{battery_device_id}_soc_factor"] = [
v / 100 v / 100
for v in self.result.akku_soc_pro_stunde[:n_points] # result starts at start_day_hour for v in self.result.akku_soc_pro_stunde[:n_points] # result starts at start_day_hour
] ]
@@ -416,8 +438,8 @@ class GeneticSolution(ConfigMixin, GeneticParametersBaseModel):
eff_ac, eff_dc, eff_dis eff_ac, eff_dc, eff_dis
) )
for mode in BatteryOperationMode: for mode in BatteryOperationMode:
mode_key = f"battery1_{mode.lower()}_op_mode" mode_key = f"{battery_device_id}_{mode.lower()}_op_mode"
factor_key = f"battery1_{mode.lower()}_op_factor" factor_key = f"{battery_device_id}_{mode.lower()}_op_factor"
if mode_key not in operation.keys(): if mode_key not in operation.keys():
operation[mode_key] = [] operation[mode_key] = []
operation[factor_key] = [] operation[factor_key] = []
@@ -438,17 +460,18 @@ class GeneticSolution(ConfigMixin, GeneticParametersBaseModel):
# eautocharge_hours_float start at hour 0 of start day # eautocharge_hours_float start at hour 0 of start day
# result.EAuto_SoC_pro_Stunde start at start_datetime.hour # result.EAuto_SoC_pro_Stunde start at start_datetime.hour
if self.eauto_obj: if self.eauto_obj:
ev_device_id = self._ev_device_id()
if self.eautocharge_hours_float is None: if self.eautocharge_hours_float is None:
# Electric vehicle is full enough. No load times. # Electric vehicle is full enough. No load times.
solution[f"{self.eauto_obj.device_id}_soc_factor"] = [ solution[f"{ev_device_id}_soc_factor"] = [
self.eauto_obj.initial_soc_percentage / 100.0 self.eauto_obj.initial_soc_percentage / 100.0
] * n_points ] * n_points
solution["genetic_ev_charge_factor"] = [0.0] * n_points solution["genetic_ev_charge_factor"] = [0.0] * n_points
# operation modes # operation modes
operation_mode = BatteryOperationMode.IDLE operation_mode = BatteryOperationMode.IDLE
for mode in BatteryOperationMode: for mode in BatteryOperationMode:
mode_key = f"{self.eauto_obj.device_id}_{mode.lower()}_op_mode" mode_key = f"{ev_device_id}_{mode.lower()}_op_mode"
factor_key = f"{self.eauto_obj.device_id}_{mode.lower()}_op_factor" factor_key = f"{ev_device_id}_{mode.lower()}_op_factor"
if mode == operation_mode: if mode == operation_mode:
solution[mode_key] = [1.0] * n_points solution[mode_key] = [1.0] * n_points
solution[factor_key] = [1.0] * n_points solution[factor_key] = [1.0] * n_points
@@ -456,7 +479,7 @@ class GeneticSolution(ConfigMixin, GeneticParametersBaseModel):
solution[mode_key] = [0.0] * n_points solution[mode_key] = [0.0] * n_points
solution[factor_key] = [0.0] * n_points solution[factor_key] = [0.0] * n_points
else: else:
solution[f"{self.eauto_obj.device_id}_soc_factor"] = [ solution[f"{ev_device_id}_soc_factor"] = [
v / 100 for v in self.result.EAuto_SoC_pro_Stunde[:n_points] v / 100 for v in self.result.EAuto_SoC_pro_Stunde[:n_points]
] ]
operation = { operation = {
@@ -472,8 +495,8 @@ class GeneticSolution(ConfigMixin, GeneticParametersBaseModel):
rate, 0.0, False rate, 0.0, False
) )
for mode in BatteryOperationMode: for mode in BatteryOperationMode:
mode_key = f"{self.eauto_obj.device_id}_{mode.lower()}_op_mode" mode_key = f"{ev_device_id}_{mode.lower()}_op_mode"
factor_key = f"{self.eauto_obj.device_id}_{mode.lower()}_op_factor" factor_key = f"{ev_device_id}_{mode.lower()}_op_factor"
if mode_key not in operation.keys(): if mode_key not in operation.keys():
operation[mode_key] = [] operation[mode_key] = []
operation[factor_key] = [] operation[factor_key] = []
@@ -494,26 +517,28 @@ class GeneticSolution(ConfigMixin, GeneticParametersBaseModel):
if self.config.devices.max_home_appliances and self.config.devices.max_home_appliances > 0: if self.config.devices.max_home_appliances and self.config.devices.max_home_appliances > 0:
# Use config and not self.washingstart as washingstart may be None (no start) # Use config and not self.washingstart as washingstart may be None (no start)
# even if configured to be started. # even if configured to be started.
homeappliance_device_id = self._homeappliance_device_id()
# result starts at start_day_hour # result starts at start_day_hour
solution["homeappliance1_energy_wh"] = self.result.Home_appliance_wh_per_hour[:n_points] solution[f"{homeappliance_device_id}_energy_wh"] = (
self.result.Home_appliance_wh_per_hour[:n_points]
)
operation = { operation = {
"homeappliance1_run_op_mode": [], f"{homeappliance_device_id}_run_op_mode": [],
"homeappliance1_run_op_factor": [], f"{homeappliance_device_id}_run_op_factor": [],
"homeappliance1_off_op_mode": [], f"{homeappliance_device_id}_off_op_mode": [],
"homeappliance1_off_op_factor": [], f"{homeappliance_device_id}_off_op_factor": [],
} }
for hour_idx, energy in enumerate(solution["homeappliance1_energy_wh"]): for hour_idx, energy in enumerate(solution[f"{homeappliance_device_id}_energy_wh"]):
if energy > 0.0: if energy > 0.0:
operation["homeappliance1_run_op_mode"].append(1.0) operation[f"{homeappliance_device_id}_run_op_mode"].append(1.0)
operation["homeappliance1_run_op_factor"].append(1.0) operation[f"{homeappliance_device_id}_run_op_factor"].append(1.0)
operation["homeappliance1_off_op_mode"].append(0.0) operation[f"{homeappliance_device_id}_off_op_mode"].append(0.0)
operation["homeappliance1_off_op_factor"].append(0.0) operation[f"{homeappliance_device_id}_off_op_factor"].append(0.0)
else: else:
operation["homeappliance1_run_op_mode"].append(0.0) operation[f"{homeappliance_device_id}_run_op_mode"].append(0.0)
operation["homeappliance1_run_op_factor"].append(0.0) operation[f"{homeappliance_device_id}_run_op_factor"].append(0.0)
operation["homeappliance1_off_op_mode"].append(1.0) operation[f"{homeappliance_device_id}_off_op_mode"].append(1.0)
operation["homeappliance1_off_op_factor"].append(1.0) operation[f"{homeappliance_device_id}_off_op_factor"].append(1.0)
for key in operation.keys(): for key in operation.keys():
if len(operation[key]) != n_points: if len(operation[key]) != n_points:
error_msg = f"instruction {key} has invalid length {len(operation[key])} - expected {n_points}" error_msg = f"instruction {key} has invalid length {len(operation[key])} - expected {n_points}"
@@ -630,7 +655,7 @@ class GeneticSolution(ConfigMixin, GeneticParametersBaseModel):
# Add battery instructions (fill rate based control) # Add battery instructions (fill rate based control)
last_operation_mode: Optional[str] = None last_operation_mode: Optional[str] = None
last_operation_mode_factor: Optional[float] = None last_operation_mode_factor: Optional[float] = None
resource_id = "battery1" resource_id = self._battery_device_id()
# ac_charge, dc_charge, discharge_allowed start at hour 0 of start day # ac_charge, dc_charge, discharge_allowed start at hour 0 of start day
logger.debug("BAT: {} - {}", resource_id, self.ac_charge[start_day_hour:]) logger.debug("BAT: {} - {}", resource_id, self.ac_charge[start_day_hour:])
for hour_idx, rate in enumerate(self.ac_charge): for hour_idx, rate in enumerate(self.ac_charge):
@@ -677,7 +702,7 @@ class GeneticSolution(ConfigMixin, GeneticParametersBaseModel):
# Add EV battery instructions (fill rate based control) # Add EV battery instructions (fill rate based control)
# eautocharge_hours_float start at hour 0 of start day # eautocharge_hours_float start at hour 0 of start day
if self.eauto_obj: if self.eauto_obj:
resource_id = self.eauto_obj.device_id resource_id = self._ev_device_id()
if self.eautocharge_hours_float is None: if self.eautocharge_hours_float is None:
# Electric vehicle is full enough. No load times. # Electric vehicle is full enough. No load times.
logger.debug("EV: {} - SoC >= min, no optimization", resource_id) logger.debug("EV: {} - SoC >= min, no optimization", resource_id)
@@ -725,7 +750,7 @@ class GeneticSolution(ConfigMixin, GeneticParametersBaseModel):
if self.config.devices.max_home_appliances and self.config.devices.max_home_appliances > 0: if self.config.devices.max_home_appliances and self.config.devices.max_home_appliances > 0:
# Use config and not self.washingstart as washingstart may be None (no start) # Use config and not self.washingstart as washingstart may be None (no start)
# even if configured to be started. # even if configured to be started.
resource_id = "homeappliance1" resource_id = self._homeappliance_device_id()
last_energy: Optional[float] = None last_energy: Optional[float] = None
for hours, energy in enumerate(self.result.Home_appliance_wh_per_hour): for hours, energy in enumerate(self.result.Home_appliance_wh_per_hour):
# hours starts at start_datetime with 0 # hours starts at start_datetime with 0