From 5fb5234e7d6fd34afbb83d44d587fc6c6a728671 Mon Sep 17 00:00:00 2001 From: Bobby Noelte Date: Wed, 18 Mar 2026 08:41:28 +0100 Subject: [PATCH] fix: device id in solution follows configuration (#964) Make device id in solution follow actual configuration. Adapt version update to new CI bump workflow design. Signed-off-by: Bobby Noelte --- .env | 2 +- Makefile | 7 +- config.yaml | 2 +- docs/_generated/openapi.md | 2 +- openapi.json | 2 +- scripts/update_version.py | 14 +++- src/akkudoktoreos/core/version.py | 2 +- .../optimization/genetic/geneticsolution.py | 79 ++++++++++++------- 8 files changed, 70 insertions(+), 40 deletions(-) diff --git a/.env b/.env index c9428a6..c840cbf 100644 --- a/.env +++ b/.env @@ -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 # ----------------------------------------------------------------------------- diff --git a/Makefile b/Makefile index d711c89..0b9178a 100644 --- a/Makefile +++ b/Makefile @@ -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" diff --git a/config.yaml b/config.yaml index be06332..93a8b38 100644 --- a/config.yaml +++ b/config.yaml @@ -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" diff --git a/docs/_generated/openapi.md b/docs/_generated/openapi.md index a8e7046..44a81a0 100644 --- a/docs/_generated/openapi.md +++ b/docs/_generated/openapi.md @@ -1,6 +1,6 @@ # Akkudoktor-EOS -**Version**: `v0.3.0` +**Version**: `v0.3.0.dev2603180681250771` **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. diff --git a/openapi.json b/openapi.json index 13dff65..ad56d8f 100644 --- a/openapi.json +++ b/openapi.json @@ -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": { diff --git a/scripts/update_version.py b/scripts/update_version.py index f92bb9c..b5c35c7 100644 --- a/scripts/update_version.py +++ b/scripts/update_version.py @@ -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 [file2 ...]") + if len(sys.argv) < 2: + print("Usage: python update_version.py [ [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) diff --git a/src/akkudoktoreos/core/version.py b/src/akkudoktoreos/core/version.py index 251274c..6f52920 100644 --- a/src/akkudoktoreos/core/version.py +++ b/src/akkudoktoreos/core/version.py @@ -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 = "" diff --git a/src/akkudoktoreos/optimization/genetic/geneticsolution.py b/src/akkudoktoreos/optimization/genetic/geneticsolution.py index c9a21c8..b03227a 100644 --- a/src/akkudoktoreos/optimization/genetic/geneticsolution.py +++ b/src/akkudoktoreos/optimization/genetic/geneticsolution.py @@ -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