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
# -----------------------------------------------------------------------------
VERSION=config.yaml
VERSION=0.3.0.dev2603180681250771
PYTHON_VERSION=3.13.9
# -----------------------------------------------------------------------------

View File

@@ -178,18 +178,15 @@ docker-build:
@docker compose build
# 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
@echo "Update version to $(VERSION) from version.py in files $(UPDATE_FILES) and doc"
$(PYTHON) ./scripts/update_version.py $(VERSION) $(UPDATE_FILES)
$(PYTHON) ./scripts/update_version.py $(VERSION)
$(PYTHON) ./scripts/convert_lightweight_tags.py
$(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.py --output-file openapi.json
$(PYTEST) -vv --finalize tests/test_doc.py
$(PRECOMMIT) run --all-files
@echo "Updated version to $(VERSION) from version.py in config files and doc"
test-version:
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).
name: "Akkudoktor-EOS"
version: "0.3.0"
version: "0.3.0.dev2603180681250771"
slug: "eos"
description: "Akkudoktor-EOS add-on"
url: "https://github.com/Akkudoktor-EOS/EOS"

View File

@@ -1,6 +1,6 @@
# Akkudoktor-EOS
**Version**: `v0.3.0`
**Version**: `v0.3.0.dev2603180681250771`
<!-- 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.

View File

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

View File

@@ -19,6 +19,11 @@ PACKAGE_DIR = PROJECT_ROOT / "src" / "akkudoktoreos"
SRC_DIR = PROJECT_ROOT / "src"
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 ---
VERSION_PATTERNS = [
@@ -146,10 +151,13 @@ def main(version: str, files: List[str]):
if __name__ == "__main__":
if len(sys.argv) < 3:
print("Usage: python update_version.py <version> <file1> [file2 ...]")
if len(sys.argv) < 2:
print("Usage: python update_version.py <version> [<file1> [file2 ...]]")
sys.exit(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)

View File

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

View File

@@ -200,6 +200,27 @@ class GeneticSolution(ConfigMixin, GeneticParametersBaseModel):
return ElectricVehicleResult(**field.to_dict())
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(
self,
ac_charge: float,
@@ -377,7 +398,8 @@ class GeneticSolution(ConfigMixin, GeneticParametersBaseModel):
)
# Add battery data
solution["battery1_soc_factor"] = [
battery_device_id = self._battery_device_id()
solution[f"{battery_device_id}_soc_factor"] = [
v / 100
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
)
for mode in BatteryOperationMode:
mode_key = f"battery1_{mode.lower()}_op_mode"
factor_key = f"battery1_{mode.lower()}_op_factor"
mode_key = f"{battery_device_id}_{mode.lower()}_op_mode"
factor_key = f"{battery_device_id}_{mode.lower()}_op_factor"
if mode_key not in operation.keys():
operation[mode_key] = []
operation[factor_key] = []
@@ -438,17 +460,18 @@ class GeneticSolution(ConfigMixin, GeneticParametersBaseModel):
# eautocharge_hours_float start at hour 0 of start day
# result.EAuto_SoC_pro_Stunde start at start_datetime.hour
if self.eauto_obj:
ev_device_id = self._ev_device_id()
if self.eautocharge_hours_float is None:
# 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
] * n_points
solution["genetic_ev_charge_factor"] = [0.0] * n_points
# operation modes
operation_mode = BatteryOperationMode.IDLE
for mode in BatteryOperationMode:
mode_key = f"{self.eauto_obj.device_id}_{mode.lower()}_op_mode"
factor_key = f"{self.eauto_obj.device_id}_{mode.lower()}_op_factor"
mode_key = f"{ev_device_id}_{mode.lower()}_op_mode"
factor_key = f"{ev_device_id}_{mode.lower()}_op_factor"
if mode == operation_mode:
solution[mode_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[factor_key] = [0.0] * n_points
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]
]
operation = {
@@ -472,8 +495,8 @@ class GeneticSolution(ConfigMixin, GeneticParametersBaseModel):
rate, 0.0, False
)
for mode in BatteryOperationMode:
mode_key = f"{self.eauto_obj.device_id}_{mode.lower()}_op_mode"
factor_key = f"{self.eauto_obj.device_id}_{mode.lower()}_op_factor"
mode_key = f"{ev_device_id}_{mode.lower()}_op_mode"
factor_key = f"{ev_device_id}_{mode.lower()}_op_factor"
if mode_key not in operation.keys():
operation[mode_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:
# Use config and not self.washingstart as washingstart may be None (no start)
# even if configured to be started.
homeappliance_device_id = self._homeappliance_device_id()
# 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 = {
"homeappliance1_run_op_mode": [],
"homeappliance1_run_op_factor": [],
"homeappliance1_off_op_mode": [],
"homeappliance1_off_op_factor": [],
f"{homeappliance_device_id}_run_op_mode": [],
f"{homeappliance_device_id}_run_op_factor": [],
f"{homeappliance_device_id}_off_op_mode": [],
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:
operation["homeappliance1_run_op_mode"].append(1.0)
operation["homeappliance1_run_op_factor"].append(1.0)
operation["homeappliance1_off_op_mode"].append(0.0)
operation["homeappliance1_off_op_factor"].append(0.0)
operation[f"{homeappliance_device_id}_run_op_mode"].append(1.0)
operation[f"{homeappliance_device_id}_run_op_factor"].append(1.0)
operation[f"{homeappliance_device_id}_off_op_mode"].append(0.0)
operation[f"{homeappliance_device_id}_off_op_factor"].append(0.0)
else:
operation["homeappliance1_run_op_mode"].append(0.0)
operation["homeappliance1_run_op_factor"].append(0.0)
operation["homeappliance1_off_op_mode"].append(1.0)
operation["homeappliance1_off_op_factor"].append(1.0)
operation[f"{homeappliance_device_id}_run_op_mode"].append(0.0)
operation[f"{homeappliance_device_id}_run_op_factor"].append(0.0)
operation[f"{homeappliance_device_id}_off_op_mode"].append(1.0)
operation[f"{homeappliance_device_id}_off_op_factor"].append(1.0)
for key in operation.keys():
if len(operation[key]) != 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)
last_operation_mode: Optional[str] = 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
logger.debug("BAT: {} - {}", resource_id, self.ac_charge[start_day_hour:])
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)
# eautocharge_hours_float start at hour 0 of start day
if self.eauto_obj:
resource_id = self.eauto_obj.device_id
resource_id = self._ev_device_id()
if self.eautocharge_hours_float is None:
# Electric vehicle is full enough. No load times.
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:
# Use config and not self.washingstart as washingstart may be None (no start)
# even if configured to be started.
resource_id = "homeappliance1"
resource_id = self._homeappliance_device_id()
last_energy: Optional[float] = None
for hours, energy in enumerate(self.result.Home_appliance_wh_per_hour):
# hours starts at start_datetime with 0