translation of battery.py v3 (#262)

This commit is contained in:
Normann 2024-12-19 14:50:19 +01:00 committed by GitHub
parent 0e122a9a49
commit 5f898e8aab
18 changed files with 684 additions and 648 deletions

View File

@ -17,12 +17,14 @@ Following sections describe how to locally start the EOS server on `http://local
Install dependencies in virtual environment: Install dependencies in virtual environment:
Linux: Linux:
```bash ```bash
python -m venv .venv python -m venv .venv
.venv/bin/pip install -r requirements.txt .venv/bin/pip install -r requirements.txt
``` ```
Windows: Windows:
```bash ```bash
python -m venv .venv python -m venv .venv
.venv\Scripts\pip install -r requirements.txt .venv\Scripts\pip install -r requirements.txt
@ -31,13 +33,17 @@ python -m venv .venv
Finally, start EOS fastapi server: Finally, start EOS fastapi server:
Linux: Linux:
```bash ```bash
.venv/bin/fastapi run --port 8503 src/akkudoktoreos/server/fastapi_server.py .venv/bin/fastapi run --port 8503 src/akkudoktoreos/server/fastapi_server.py
``` ```
Windows: Windows:
``` ```
.venv\Scripts\fastapi run --port 8503 src/akkudoktoreos/server/fastapi_server.py .venv\Scripts\fastapi run --port 8503 src/akkudoktoreos/server/fastapi_server.py
``` ```
### Docker ### Docker
```bash ```bash
@ -67,7 +73,7 @@ If the configuration keys in the `EOS.config.json` file are missing or different
This project uses various classes to simulate and optimize the components of an energy system. Each class represents a specific aspect of the system, as described below: This project uses various classes to simulate and optimize the components of an energy system. Each class represents a specific aspect of the system, as described below:
- `PVAkku`: Simulates a battery storage system, including capacity, state of charge, and now charge and discharge losses. - `Battery`: Simulates a battery storage system, including capacity, state of charge, and now charge and discharge losses.
- `PVForecast`: Provides forecast data for photovoltaic generation, based on weather data and historical generation data. - `PVForecast`: Provides forecast data for photovoltaic generation, based on weather data and historical generation data.
@ -89,7 +95,6 @@ Each class is designed to be easily customized and extended to integrate additio
See the Swagger API documentation for detailed information: [EOS OpenAPI Spec](https://petstore3.swagger.io/?url=https://raw.githubusercontent.com/Akkudoktor-EOS/EOS/refs/heads/main/docs/akkudoktoreos/openapi.json) See the Swagger API documentation for detailed information: [EOS OpenAPI Spec](https://petstore3.swagger.io/?url=https://raw.githubusercontent.com/Akkudoktor-EOS/EOS/refs/heads/main/docs/akkudoktoreos/openapi.json)
## Further resources ## Further resources
- [Installation guide (de)](https://meintechblog.de/2024/09/05/andreas-schmitz-joerg-installiert-mein-energieoptimierungssystem/) - [Installation guide (de)](https://meintechblog.de/2024/09/05/andreas-schmitz-joerg-installiert-mein-energieoptimierungssystem/)

View File

@ -2091,7 +2091,7 @@
"title": "Battery Capacity", "title": "Battery Capacity",
"description": "Battery capacity [Wh]." "description": "Battery capacity [Wh]."
}, },
"battery_soc_start": { "battery_initial_soc": {
"anyOf": [ "anyOf": [
{ {
"type": "integer" "type": "integer"
@ -2100,7 +2100,7 @@
"type": "null" "type": "null"
} }
], ],
"title": "Battery Soc Start", "title": "Battery Initial Soc",
"description": "Battery initial state of charge [%]." "description": "Battery initial state of charge [%]."
}, },
"battery_soc_min": { "battery_soc_min": {
@ -2127,7 +2127,7 @@
"title": "Battery Soc Max", "title": "Battery Soc Max",
"description": "Battery maximum state of charge [%]." "description": "Battery maximum state of charge [%]."
}, },
"battery_charge_efficiency": { "battery_charging_efficiency": {
"anyOf": [ "anyOf": [
{ {
"type": "number" "type": "number"
@ -2136,10 +2136,10 @@
"type": "null" "type": "null"
} }
], ],
"title": "Battery Charge Efficiency", "title": "Battery Charging Efficiency",
"description": "Battery charging efficiency [%]." "description": "Battery charging efficiency [%]."
}, },
"battery_discharge_efficiency": { "battery_discharging_efficiency": {
"anyOf": [ "anyOf": [
{ {
"type": "number" "type": "number"
@ -2148,10 +2148,10 @@
"type": "null" "type": "null"
} }
], ],
"title": "Battery Discharge Efficiency", "title": "Battery Discharging Efficiency",
"description": "Battery discharging efficiency [%]." "description": "Battery discharging efficiency [%]."
}, },
"battery_charge_power_max": { "battery_max_charging_power": {
"anyOf": [ "anyOf": [
{ {
"type": "integer" "type": "integer"
@ -2160,7 +2160,7 @@
"type": "null" "type": "null"
} }
], ],
"title": "Battery Charge Power Max", "title": "Battery Max Charging Power",
"description": "Battery maximum charge power [W]." "description": "Battery maximum charge power [W]."
}, },
"bev_provider": { "bev_provider": {
@ -2187,7 +2187,7 @@
"title": "Bev Capacity", "title": "Bev Capacity",
"description": "Battery Electric Vehicle capacity [Wh]." "description": "Battery Electric Vehicle capacity [Wh]."
}, },
"bev_soc_start": { "bev_initial_soc": {
"anyOf": [ "anyOf": [
{ {
"type": "integer" "type": "integer"
@ -2196,7 +2196,7 @@
"type": "null" "type": "null"
} }
], ],
"title": "Bev Soc Start", "title": "Bev Initial Soc",
"description": "Battery Electric Vehicle initial state of charge [%]." "description": "Battery Electric Vehicle initial state of charge [%]."
}, },
"bev_soc_max": { "bev_soc_max": {
@ -2211,7 +2211,7 @@
"title": "Bev Soc Max", "title": "Bev Soc Max",
"description": "Battery Electric Vehicle maximum state of charge [%]." "description": "Battery Electric Vehicle maximum state of charge [%]."
}, },
"bev_charge_efficiency": { "bev_charging_efficiency": {
"anyOf": [ "anyOf": [
{ {
"type": "number" "type": "number"
@ -2220,10 +2220,10 @@
"type": "null" "type": "null"
} }
], ],
"title": "Bev Charge Efficiency", "title": "Bev Charging Efficiency",
"description": "Battery Electric Vehicle charging efficiency [%]." "description": "Battery Electric Vehicle charging efficiency [%]."
}, },
"bev_discharge_efficiency": { "bev_discharging_efficiency": {
"anyOf": [ "anyOf": [
{ {
"type": "number" "type": "number"
@ -2232,10 +2232,10 @@
"type": "null" "type": "null"
} }
], ],
"title": "Bev Discharge Efficiency", "title": "Bev Discharging Efficiency",
"description": "Battery Electric Vehicle discharging efficiency [%]." "description": "Battery Electric Vehicle discharging efficiency [%]."
}, },
"bev_charge_power_max": { "bev_max_charging_power": {
"anyOf": [ "anyOf": [
{ {
"type": "integer" "type": "integer"
@ -2244,7 +2244,7 @@
"type": "null" "type": "null"
} }
], ],
"title": "Bev Charge Power Max", "title": "Bev Max Charging Power",
"description": "Battery Electric Vehicle maximum charge power [W]." "description": "Battery Electric Vehicle maximum charge power [W]."
}, },
"dishwasher_provider": { "dishwasher_provider": {
@ -2485,28 +2485,28 @@
"title": "ConfigEOS", "title": "ConfigEOS",
"description": "Singleton configuration handler for the EOS application.\n\nConfigEOS extends `SettingsEOS` with support for default configuration paths and automatic\ninitialization.\n\n`ConfigEOS` ensures that only one instance of the class is created throughout the application,\nallowing consistent access to EOS configuration settings. This singleton instance loads\nconfiguration data from a predefined set of directories or creates a default configuration if\nnone is found.\n\nInitialization Process:\n - Upon instantiation, the singleton instance attempts to load a configuration file in this order:\n 1. The directory specified by the `EOS_DIR` environment variable.\n 2. A platform specific default directory for EOS.\n 3. The current working directory.\n - The first available configuration file found in these directories is loaded.\n - If no configuration file is found, a default configuration file is created in the platform\n specific default directory, and default settings are loaded into it.\n\nAttributes from the loaded configuration are accessible directly as instance attributes of\n`ConfigEOS`, providing a centralized, shared configuration object for EOS.\n\nSingleton Behavior:\n - This class uses the `SingletonMixin` to ensure that all requests for `ConfigEOS` return\n the same instance, which contains the most up-to-date configuration. Modifying the configuration\n in one part of the application reflects across all references to this class.\n\nAttributes:\n _settings (ClassVar[SettingsEOS]): Holds application-wide settings.\n _file_settings (ClassVar[SettingsEOS]): Stores configuration loaded from file.\n config_folder_path (Optional[Path]): Path to the configuration directory.\n config_file_path (Optional[Path]): Path to the configuration file.\n\nRaises:\n FileNotFoundError: If no configuration file is found, and creating a default configuration fails.\n\nExample:\n To initialize and access configuration attributes (only one instance is created):\n ```python\n config_eos = ConfigEOS() # Always returns the same instance\n print(config_eos.prediction_hours) # Access a setting from the loaded configuration\n ```" "description": "Singleton configuration handler for the EOS application.\n\nConfigEOS extends `SettingsEOS` with support for default configuration paths and automatic\ninitialization.\n\n`ConfigEOS` ensures that only one instance of the class is created throughout the application,\nallowing consistent access to EOS configuration settings. This singleton instance loads\nconfiguration data from a predefined set of directories or creates a default configuration if\nnone is found.\n\nInitialization Process:\n - Upon instantiation, the singleton instance attempts to load a configuration file in this order:\n 1. The directory specified by the `EOS_DIR` environment variable.\n 2. A platform specific default directory for EOS.\n 3. The current working directory.\n - The first available configuration file found in these directories is loaded.\n - If no configuration file is found, a default configuration file is created in the platform\n specific default directory, and default settings are loaded into it.\n\nAttributes from the loaded configuration are accessible directly as instance attributes of\n`ConfigEOS`, providing a centralized, shared configuration object for EOS.\n\nSingleton Behavior:\n - This class uses the `SingletonMixin` to ensure that all requests for `ConfigEOS` return\n the same instance, which contains the most up-to-date configuration. Modifying the configuration\n in one part of the application reflects across all references to this class.\n\nAttributes:\n _settings (ClassVar[SettingsEOS]): Holds application-wide settings.\n _file_settings (ClassVar[SettingsEOS]): Stores configuration loaded from file.\n config_folder_path (Optional[Path]): Path to the configuration directory.\n config_file_path (Optional[Path]): Path to the configuration file.\n\nRaises:\n FileNotFoundError: If no configuration file is found, and creating a default configuration fails.\n\nExample:\n To initialize and access configuration attributes (only one instance is created):\n ```python\n config_eos = ConfigEOS() # Always returns the same instance\n print(config_eos.prediction_hours) # Access a setting from the loaded configuration\n ```"
}, },
"EAutoParameters": { "ElectricVehicleParameters": {
"properties": { "properties": {
"kapazitaet_wh": { "capacity_wh": {
"type": "integer", "type": "integer",
"exclusiveMinimum": 0.0, "exclusiveMinimum": 0.0,
"title": "Kapazitaet Wh", "title": "Capacity Wh",
"description": "An integer representing the capacity of the battery in watt-hours." "description": "An integer representing the capacity of the battery in watt-hours."
}, },
"lade_effizienz": { "charging_efficiency": {
"type": "number", "type": "number",
"maximum": 1.0, "maximum": 1.0,
"exclusiveMinimum": 0.0, "exclusiveMinimum": 0.0,
"title": "Lade Effizienz", "title": "Charging Efficiency",
"description": "A float representing the charging efficiency of the battery.", "description": "A float representing the charging efficiency of the battery.",
"default": 0.88 "default": 0.88
}, },
"entlade_effizienz": { "discharging_efficiency": {
"type": "number", "type": "number",
"title": "Entlade Effizienz", "title": "Discharging Efficiency",
"default": 1.0 "default": 1.0
}, },
"max_ladeleistung_w": { "max_charge_power_w": {
"anyOf": [ "anyOf": [
{ {
"type": "number", "type": "number",
@ -2516,38 +2516,41 @@
"type": "null" "type": "null"
} }
], ],
"title": "Max Ladeleistung W", "title": "Max Charge Power W",
"description": "An integer representing the charging power of the battery in watts." "description": "Maximum charging power in watts.",
"default": 5000
}, },
"start_soc_prozent": { "initial_soc_percentage": {
"type": "integer", "type": "integer",
"maximum": 100.0, "maximum": 100.0,
"minimum": 0.0, "minimum": 0.0,
"title": "Start Soc Prozent", "title": "Initial Soc Percentage",
"description": "An integer representing the current state of charge (SOC) of the battery in percentage.", "description": "An integer representing the current state of charge (SOC) of the battery in percentage.",
"default": 0 "default": 0
}, },
"min_soc_prozent": { "min_soc_percentage": {
"type": "integer", "type": "integer",
"maximum": 100.0, "maximum": 100.0,
"minimum": 0.0, "minimum": 0.0,
"title": "Min Soc Prozent", "title": "Min Soc Percentage",
"description": "An integer representing the minimum state of charge (SOC) of the battery in percentage.", "description": "An integer representing the minimum state of charge (SOC) of the battery in percentage.",
"default": 0 "default": 0
}, },
"max_soc_prozent": { "max_soc_percentage": {
"type": "integer", "type": "integer",
"maximum": 100.0, "maximum": 100.0,
"minimum": 0.0, "minimum": 0.0,
"title": "Max Soc Prozent", "title": "Max Soc Percentage",
"description": "An integer representing the maximum state of charge (SOC) of the battery in percentage.",
"default": 100 "default": 100
} }
}, },
"type": "object", "type": "object",
"required": ["kapazitaet_wh"], "required": ["capacity_wh"],
"title": "EAutoParameters" "title": "ElectricVehicleParameters",
"description": "Parameters specific to an electric vehicle (EV)."
}, },
"EAutoResult": { "ElectricVehicleResult": {
"properties": { "properties": {
"charge_array": { "charge_array": {
"items": { "items": {
@ -2555,7 +2558,7 @@
}, },
"type": "array", "type": "array",
"title": "Charge Array", "title": "Charge Array",
"description": "Indicates for each hour whether the EV is charging (`0` for no charging, `1` for charging)." "description": "Hourly charging status (0 for no charging, 1 for charging)."
}, },
"discharge_array": { "discharge_array": {
"items": { "items": {
@ -2563,58 +2566,58 @@
}, },
"type": "array", "type": "array",
"title": "Discharge Array", "title": "Discharge Array",
"description": "Indicates for each hour whether the EV is discharging (`0` for no discharging, `1` for discharging)." "description": "Hourly discharging status (0 for no discharging, 1 for discharging)."
}, },
"entlade_effizienz": { "discharging_efficiency": {
"type": "number", "type": "number",
"title": "Entlade Effizienz", "title": "Discharging Efficiency",
"description": "The discharge efficiency as a float." "description": "The discharge efficiency as a float.."
}, },
"hours": { "hours": {
"type": "integer", "type": "integer",
"title": "Hours", "title": "Hours",
"description": "Amount of hours the simulation is done for." "description": "Number of hours in the simulation."
}, },
"kapazitaet_wh": { "capacity_wh": {
"type": "integer", "type": "integer",
"title": "Kapazitaet Wh", "title": "Capacity Wh",
"description": "The capacity of the EV\u2019s battery in watt-hours." "description": "Capacity of the EV\u2019s battery in watt-hours."
}, },
"lade_effizienz": { "charging_efficiency": {
"type": "number", "type": "number",
"title": "Lade Effizienz", "title": "Charging Efficiency",
"description": "The charging efficiency as a float." "description": "Charging efficiency as a float.."
}, },
"max_ladeleistung_w": { "max_charge_power_w": {
"type": "integer", "type": "integer",
"title": "Max Ladeleistung W", "title": "Max Charge Power W",
"description": "The maximum charging power of the EV in watts." "description": "Maximum charging power in watts."
}, },
"soc_wh": { "soc_wh": {
"type": "number", "type": "number",
"title": "Soc Wh", "title": "Soc Wh",
"description": "The state of charge of the battery in watt-hours at the start of the simulation." "description": "State of charge of the battery in watt-hours at the start of the simulation."
}, },
"start_soc_prozent": { "initial_soc_percentage": {
"type": "integer", "type": "integer",
"title": "Start Soc Prozent", "title": "Initial Soc Percentage",
"description": "The state of charge of the battery in percentage at the start of the simulation." "description": "State of charge at the start of the simulation in percentage."
} }
}, },
"type": "object", "type": "object",
"required": [ "required": [
"charge_array", "charge_array",
"discharge_array", "discharge_array",
"entlade_effizienz", "discharging_efficiency",
"hours", "hours",
"kapazitaet_wh", "capacity_wh",
"lade_effizienz", "charging_efficiency",
"max_ladeleistung_w", "max_charge_power_w",
"soc_wh", "soc_wh",
"start_soc_prozent" "initial_soc_percentage"
], ],
"title": "EAutoResult", "title": "ElectricVehicleResult",
"description": "This object contains information related to the electric vehicle and its charging and discharging behavior." "description": "Result class containing information related to the electric vehicle's charging and discharging behavior."
}, },
"EnergieManagementSystemParameters": { "EnergieManagementSystemParameters": {
"properties": { "properties": {
@ -2766,7 +2769,7 @@
"$ref": "#/components/schemas/EnergieManagementSystemParameters" "$ref": "#/components/schemas/EnergieManagementSystemParameters"
}, },
"pv_akku": { "pv_akku": {
"$ref": "#/components/schemas/PVAkkuParameters" "$ref": "#/components/schemas/SolarPanelBatteryParameters"
}, },
"inverter": { "inverter": {
"$ref": "#/components/schemas/InverterParameters", "$ref": "#/components/schemas/InverterParameters",
@ -2777,7 +2780,7 @@
"eauto": { "eauto": {
"anyOf": [ "anyOf": [
{ {
"$ref": "#/components/schemas/EAutoParameters" "$ref": "#/components/schemas/ElectricVehicleParameters"
}, },
{ {
"type": "null" "type": "null"
@ -2876,7 +2879,7 @@
"eauto_obj": { "eauto_obj": {
"anyOf": [ "anyOf": [
{ {
"$ref": "#/components/schemas/EAutoResult" "$ref": "#/components/schemas/ElectricVehicleResult"
}, },
{ {
"type": "null" "type": "null"
@ -2923,71 +2926,6 @@
"title": "OptimizeResponse", "title": "OptimizeResponse",
"description": "**Note**: The first value of \"Last_Wh_per_hour\", \"Netzeinspeisung_Wh_per_hour\", and \"Netzbezug_Wh_per_hour\", will be set to null in the JSON output and represented as NaN or None in the corresponding classes' data returns. This approach is adopted to ensure that the current hour's processing remains unchanged." "description": "**Note**: The first value of \"Last_Wh_per_hour\", \"Netzeinspeisung_Wh_per_hour\", and \"Netzbezug_Wh_per_hour\", will be set to null in the JSON output and represented as NaN or None in the corresponding classes' data returns. This approach is adopted to ensure that the current hour's processing remains unchanged."
}, },
"PVAkkuParameters": {
"properties": {
"kapazitaet_wh": {
"type": "integer",
"exclusiveMinimum": 0.0,
"title": "Kapazitaet Wh",
"description": "An integer representing the capacity of the battery in watt-hours."
},
"lade_effizienz": {
"type": "number",
"maximum": 1.0,
"exclusiveMinimum": 0.0,
"title": "Lade Effizienz",
"description": "A float representing the charging efficiency of the battery.",
"default": 0.88
},
"entlade_effizienz": {
"type": "number",
"maximum": 1.0,
"exclusiveMinimum": 0.0,
"title": "Entlade Effizienz",
"default": 0.88
},
"max_ladeleistung_w": {
"anyOf": [
{
"type": "number",
"exclusiveMinimum": 0.0
},
{
"type": "null"
}
],
"title": "Max Ladeleistung W",
"description": "An integer representing the charging power of the battery in watts.",
"default": 5000
},
"start_soc_prozent": {
"type": "integer",
"maximum": 100.0,
"minimum": 0.0,
"title": "Start Soc Prozent",
"description": "An integer representing the state of charge of the battery at the **start** of the current hour (not the current state).",
"default": 0
},
"min_soc_prozent": {
"type": "integer",
"maximum": 100.0,
"minimum": 0.0,
"title": "Min Soc Prozent",
"description": "An integer representing the minimum state of charge (SOC) of the battery in percentage.",
"default": 0
},
"max_soc_prozent": {
"type": "integer",
"maximum": 100.0,
"minimum": 0.0,
"title": "Max Soc Prozent",
"default": 100
}
},
"type": "object",
"required": ["kapazitaet_wh"],
"title": "PVAkkuParameters"
},
"SettingsEOS": { "SettingsEOS": {
"properties": { "properties": {
"server_fastapi_host": { "server_fastapi_host": {
@ -4568,7 +4506,7 @@
"title": "Battery Capacity", "title": "Battery Capacity",
"description": "Battery capacity [Wh]." "description": "Battery capacity [Wh]."
}, },
"battery_soc_start": { "battery_initial_soc": {
"anyOf": [ "anyOf": [
{ {
"type": "integer" "type": "integer"
@ -4577,7 +4515,7 @@
"type": "null" "type": "null"
} }
], ],
"title": "Battery Soc Start", "title": "Battery Initial Soc",
"description": "Battery initial state of charge [%]." "description": "Battery initial state of charge [%]."
}, },
"battery_soc_min": { "battery_soc_min": {
@ -4604,7 +4542,7 @@
"title": "Battery Soc Max", "title": "Battery Soc Max",
"description": "Battery maximum state of charge [%]." "description": "Battery maximum state of charge [%]."
}, },
"battery_charge_efficiency": { "battery_charging_efficiency": {
"anyOf": [ "anyOf": [
{ {
"type": "number" "type": "number"
@ -4613,10 +4551,10 @@
"type": "null" "type": "null"
} }
], ],
"title": "Battery Charge Efficiency", "title": "Battery Charging Efficiency",
"description": "Battery charging efficiency [%]." "description": "Battery charging efficiency [%]."
}, },
"battery_discharge_efficiency": { "battery_discharging_efficiency": {
"anyOf": [ "anyOf": [
{ {
"type": "number" "type": "number"
@ -4625,10 +4563,10 @@
"type": "null" "type": "null"
} }
], ],
"title": "Battery Discharge Efficiency", "title": "Battery Discharging Efficiency",
"description": "Battery discharging efficiency [%]." "description": "Battery discharging efficiency [%]."
}, },
"battery_charge_power_max": { "battery_max_charging_power": {
"anyOf": [ "anyOf": [
{ {
"type": "integer" "type": "integer"
@ -4637,7 +4575,7 @@
"type": "null" "type": "null"
} }
], ],
"title": "Battery Charge Power Max", "title": "Battery Max Charging Power",
"description": "Battery maximum charge power [W]." "description": "Battery maximum charge power [W]."
}, },
"bev_provider": { "bev_provider": {
@ -4664,7 +4602,7 @@
"title": "Bev Capacity", "title": "Bev Capacity",
"description": "Battery Electric Vehicle capacity [Wh]." "description": "Battery Electric Vehicle capacity [Wh]."
}, },
"bev_soc_start": { "bev_initial_soc": {
"anyOf": [ "anyOf": [
{ {
"type": "integer" "type": "integer"
@ -4673,7 +4611,7 @@
"type": "null" "type": "null"
} }
], ],
"title": "Bev Soc Start", "title": "Bev Initial Soc",
"description": "Battery Electric Vehicle initial state of charge [%]." "description": "Battery Electric Vehicle initial state of charge [%]."
}, },
"bev_soc_max": { "bev_soc_max": {
@ -4688,7 +4626,7 @@
"title": "Bev Soc Max", "title": "Bev Soc Max",
"description": "Battery Electric Vehicle maximum state of charge [%]." "description": "Battery Electric Vehicle maximum state of charge [%]."
}, },
"bev_charge_efficiency": { "bev_charging_efficiency": {
"anyOf": [ "anyOf": [
{ {
"type": "number" "type": "number"
@ -4697,10 +4635,10 @@
"type": "null" "type": "null"
} }
], ],
"title": "Bev Charge Efficiency", "title": "Bev Charging Efficiency",
"description": "Battery Electric Vehicle charging efficiency [%]." "description": "Battery Electric Vehicle charging efficiency [%]."
}, },
"bev_discharge_efficiency": { "bev_discharging_efficiency": {
"anyOf": [ "anyOf": [
{ {
"type": "number" "type": "number"
@ -4709,10 +4647,10 @@
"type": "null" "type": "null"
} }
], ],
"title": "Bev Discharge Efficiency", "title": "Bev Discharging Efficiency",
"description": "Battery Electric Vehicle discharging efficiency [%]." "description": "Battery Electric Vehicle discharging efficiency [%]."
}, },
"bev_charge_power_max": { "bev_max_charging_power": {
"anyOf": [ "anyOf": [
{ {
"type": "integer" "type": "integer"
@ -4721,7 +4659,7 @@
"type": "null" "type": "null"
} }
], ],
"title": "Bev Charge Power Max", "title": "Bev Max Charging Power",
"description": "Battery Electric Vehicle maximum charge power [W]." "description": "Battery Electric Vehicle maximum charge power [W]."
}, },
"dishwasher_provider": { "dishwasher_provider": {
@ -5007,6 +4945,73 @@
"title": "SimulationResult", "title": "SimulationResult",
"description": "This object contains the results of the simulation and provides insights into various parameters over the entire forecast period." "description": "This object contains the results of the simulation and provides insights into various parameters over the entire forecast period."
}, },
"SolarPanelBatteryParameters": {
"properties": {
"capacity_wh": {
"type": "integer",
"exclusiveMinimum": 0.0,
"title": "Capacity Wh",
"description": "An integer representing the capacity of the battery in watt-hours."
},
"charging_efficiency": {
"type": "number",
"maximum": 1.0,
"exclusiveMinimum": 0.0,
"title": "Charging Efficiency",
"description": "A float representing the charging efficiency of the battery.",
"default": 0.88
},
"discharging_efficiency": {
"type": "number",
"maximum": 1.0,
"exclusiveMinimum": 0.0,
"title": "Discharging Efficiency",
"description": "A float representing the discharge efficiency of the battery.",
"default": 0.88
},
"max_charge_power_w": {
"anyOf": [
{
"type": "number",
"exclusiveMinimum": 0.0
},
{
"type": "null"
}
],
"title": "Max Charge Power W",
"description": "Maximum charging power in watts.",
"default": 5000
},
"initial_soc_percentage": {
"type": "integer",
"maximum": 100.0,
"minimum": 0.0,
"title": "Initial Soc Percentage",
"description": "An integer representing the state of charge of the battery at the **start** of the current hour (not the current state).",
"default": 0
},
"min_soc_percentage": {
"type": "integer",
"maximum": 100.0,
"minimum": 0.0,
"title": "Min Soc Percentage",
"description": "An integer representing the minimum state of charge (SOC) of the battery in percentage.",
"default": 0
},
"max_soc_percentage": {
"type": "integer",
"maximum": 100.0,
"minimum": 0.0,
"title": "Max Soc Percentage",
"description": "An integer representing the maximum state of charge (SOC) of the battery in percentage.",
"default": 100
}
},
"type": "object",
"required": ["capacity_wh"],
"title": "SolarPanelBatteryParameters"
},
"ValidationError": { "ValidationError": {
"properties": { "properties": {
"loc": { "loc": {

View File

@ -125,16 +125,16 @@ def prepare_optimization_real_parameters() -> OptimizationParameters:
"strompreis_euro_pro_wh": strompreis_euro_pro_wh, "strompreis_euro_pro_wh": strompreis_euro_pro_wh,
}, },
"pv_akku": { "pv_akku": {
"kapazitaet_wh": 26400, "capacity_wh": 26400,
"start_soc_prozent": 15, "initial_soc_percentage": 15,
"min_soc_prozent": 15, "min_soc_percentage": 15,
}, },
"eauto": { "eauto": {
"min_soc_prozent": 50, "min_soc_percentage": 50,
"kapazitaet_wh": 60000, "capacity_wh": 60000,
"lade_effizienz": 0.95, "charging_efficiency": 0.95,
"max_ladeleistung_w": 11040, "max_charge_power_w": 11040,
"start_soc_prozent": 5, "initial_soc_percentage": 5,
}, },
"temperature_forecast": temperature_forecast, "temperature_forecast": temperature_forecast,
"start_solution": start_solution, "start_solution": start_solution,
@ -276,16 +276,16 @@ def prepare_optimization_parameters() -> OptimizationParameters:
"strompreis_euro_pro_wh": strompreis_euro_pro_wh, "strompreis_euro_pro_wh": strompreis_euro_pro_wh,
}, },
"pv_akku": { "pv_akku": {
"kapazitaet_wh": 26400, "capacity_wh": 26400,
"start_soc_prozent": 15, "initial_soc_percentage": 15,
"min_soc_prozent": 15, "min_soc_percentage": 15,
}, },
"eauto": { "eauto": {
"min_soc_prozent": 50, "min_soc_percentage": 50,
"kapazitaet_wh": 60000, "capacity_wh": 60000,
"lade_effizienz": 0.95, "charging_efficiency": 0.95,
"max_ladeleistung_w": 11040, "max_charge_power_w": 11040,
"start_soc_prozent": 5, "initial_soc_percentage": 5,
}, },
"temperature_forecast": temperature_forecast, "temperature_forecast": temperature_forecast,
"start_solution": start_solution, "start_solution": start_solution,

View File

@ -8,7 +8,7 @@ from typing_extensions import Self
from akkudoktoreos.core.coreabc import ConfigMixin, PredictionMixin, SingletonMixin from akkudoktoreos.core.coreabc import ConfigMixin, PredictionMixin, SingletonMixin
from akkudoktoreos.core.pydantic import PydanticBaseModel from akkudoktoreos.core.pydantic import PydanticBaseModel
from akkudoktoreos.devices.battery import PVAkku from akkudoktoreos.devices.battery import Battery
from akkudoktoreos.devices.generic import HomeAppliance from akkudoktoreos.devices.generic import HomeAppliance
from akkudoktoreos.devices.inverter import Inverter from akkudoktoreos.devices.inverter import Inverter
from akkudoktoreos.utils.datetimeutil import to_datetime from akkudoktoreos.utils.datetimeutil import to_datetime
@ -152,8 +152,8 @@ class EnergieManagementSystem(SingletonMixin, ConfigMixin, PredictionMixin, Pyda
# TODO: Move to devices # TODO: Move to devices
# ------------------------- # -------------------------
akku: Optional[PVAkku] = Field(default=None, description="TBD.") akku: Optional[Battery] = Field(default=None, description="TBD.")
eauto: Optional[PVAkku] = Field(default=None, description="TBD.") eauto: Optional[Battery] = Field(default=None, description="TBD.")
home_appliance: Optional[HomeAppliance] = Field(default=None, description="TBD.") home_appliance: Optional[HomeAppliance] = Field(default=None, description="TBD.")
inverter: Optional[Inverter] = Field(default=None, description="TBD.") inverter: Optional[Inverter] = Field(default=None, description="TBD.")
@ -168,7 +168,7 @@ class EnergieManagementSystem(SingletonMixin, ConfigMixin, PredictionMixin, Pyda
def set_parameters( def set_parameters(
self, self,
parameters: EnergieManagementSystemParameters, parameters: EnergieManagementSystemParameters,
eauto: Optional[PVAkku] = None, eauto: Optional[Battery] = None,
home_appliance: Optional[HomeAppliance] = None, home_appliance: Optional[HomeAppliance] = None,
inverter: Optional[Inverter] = None, inverter: Optional[Inverter] = None,
) -> None: ) -> None:
@ -323,9 +323,9 @@ class EnergieManagementSystem(SingletonMixin, ConfigMixin, PredictionMixin, Pyda
# Set initial state # Set initial state
if self.akku: if self.akku:
akku_soc_pro_stunde[0] = self.akku.ladezustand_in_prozent() akku_soc_pro_stunde[0] = self.akku.current_soc_percentage()
if self.eauto: if self.eauto:
eauto_soc_pro_stunde[0] = self.eauto.ladezustand_in_prozent() eauto_soc_pro_stunde[0] = self.eauto.current_soc_percentage()
for stunde in range(start_stunde, ende): for stunde in range(start_stunde, ende):
stunde_since_now = stunde - start_stunde stunde_since_now = stunde - start_stunde
@ -343,12 +343,12 @@ class EnergieManagementSystem(SingletonMixin, ConfigMixin, PredictionMixin, Pyda
# E-Auto handling # E-Auto handling
if self.eauto: if self.eauto:
if self.ev_charge_hours[stunde] > 0: if self.ev_charge_hours[stunde] > 0:
geladene_menge_eauto, verluste_eauto = self.eauto.energie_laden( geladene_menge_eauto, verluste_eauto = self.eauto.charge_energy(
None, stunde, relative_power=self.ev_charge_hours[stunde] None, stunde, relative_power=self.ev_charge_hours[stunde]
) )
verbrauch += geladene_menge_eauto verbrauch += geladene_menge_eauto
verluste_wh_pro_stunde[stunde_since_now] += verluste_eauto verluste_wh_pro_stunde[stunde_since_now] += verluste_eauto
eauto_soc_pro_stunde[stunde_since_now] = self.eauto.ladezustand_in_prozent() eauto_soc_pro_stunde[stunde_since_now] = self.eauto.current_soc_percentage()
# Process inverter logic # Process inverter logic
netzeinspeisung, netzbezug, verluste, eigenverbrauch = (0.0, 0.0, 0.0, 0.0) netzeinspeisung, netzbezug, verluste, eigenverbrauch = (0.0, 0.0, 0.0, 0.0)
@ -363,10 +363,10 @@ class EnergieManagementSystem(SingletonMixin, ConfigMixin, PredictionMixin, Pyda
# AC PV Battery Charge # AC PV Battery Charge
if self.akku and self.ac_charge_hours[stunde] > 0.0: if self.akku and self.ac_charge_hours[stunde] > 0.0:
self.akku.set_charge_allowed_for_hour(1, stunde) self.akku.set_charge_allowed_for_hour(1, stunde)
geladene_menge, verluste_wh = self.akku.energie_laden( geladene_menge, verluste_wh = self.akku.charge_energy(
None, stunde, relative_power=self.ac_charge_hours[stunde] None, stunde, relative_power=self.ac_charge_hours[stunde]
) )
# print(stunde, " ", geladene_menge, " ",self.ac_charge_hours[stunde]," ",self.akku.ladezustand_in_prozent()) # print(stunde, " ", geladene_menge, " ",self.ac_charge_hours[stunde]," ",self.akku.current_soc_percentage())
verbrauch += geladene_menge verbrauch += geladene_menge
verbrauch += verluste_wh verbrauch += verluste_wh
netzbezug += geladene_menge netzbezug += geladene_menge
@ -388,7 +388,7 @@ class EnergieManagementSystem(SingletonMixin, ConfigMixin, PredictionMixin, Pyda
# Akku SOC tracking # Akku SOC tracking
if self.akku: if self.akku:
akku_soc_pro_stunde[stunde_since_now] = self.akku.ladezustand_in_prozent() akku_soc_pro_stunde[stunde_since_now] = self.akku.current_soc_percentage()
else: else:
akku_soc_pro_stunde[stunde_since_now] = 0.0 akku_soc_pro_stunde[stunde_since_now] = 0.0

View File

@ -10,345 +10,279 @@ from akkudoktoreos.utils.utils import NumpyEncoder
logger = get_logger(__name__) logger = get_logger(__name__)
def max_ladeleistung_w_field(default: Optional[float] = None) -> Optional[float]: def max_charging_power_field(description: Optional[str] = None) -> float:
if description is None:
description = "Maximum charging power in watts."
return Field( return Field(
default=default, default=5000,
gt=0, gt=0,
description="An integer representing the charging power of the battery in watts.", description=description,
) )
def start_soc_prozent_field(description: str) -> int: def initial_soc_percentage_field(description: str) -> int:
return Field(default=0, ge=0, le=100, description=description) return Field(default=0, ge=0, le=100, description=description)
class BaseAkkuParameters(BaseModel): class BaseBatteryParameters(BaseModel):
kapazitaet_wh: int = Field( """Base class for battery parameters with fields for capacity, efficiency, and state of charge."""
capacity_wh: int = Field(
gt=0, description="An integer representing the capacity of the battery in watt-hours." gt=0, description="An integer representing the capacity of the battery in watt-hours."
) )
lade_effizienz: float = Field( charging_efficiency: float = Field(
default=0.88, default=0.88,
gt=0, gt=0,
le=1, le=1,
description="A float representing the charging efficiency of the battery.", description="A float representing the charging efficiency of the battery.",
) )
entlade_effizienz: float = Field(default=0.88, gt=0, le=1) discharging_efficiency: float = Field(
max_ladeleistung_w: Optional[float] = max_ladeleistung_w_field() default=0.88,
start_soc_prozent: int = start_soc_prozent_field( gt=0,
le=1,
description="A float representing the discharge efficiency of the battery.",
)
max_charge_power_w: Optional[float] = max_charging_power_field()
initial_soc_percentage: int = initial_soc_percentage_field(
"An integer representing the state of charge of the battery at the **start** of the current hour (not the current state)." "An integer representing the state of charge of the battery at the **start** of the current hour (not the current state)."
) )
min_soc_prozent: int = Field( min_soc_percentage: int = Field(
default=0, default=0,
ge=0, ge=0,
le=100, le=100,
description="An integer representing the minimum state of charge (SOC) of the battery in percentage.", description="An integer representing the minimum state of charge (SOC) of the battery in percentage.",
) )
max_soc_prozent: int = Field(default=100, ge=0, le=100) max_soc_percentage: int = Field(
default=100,
ge=0,
le=100,
description="An integer representing the maximum state of charge (SOC) of the battery in percentage.",
)
class PVAkkuParameters(BaseAkkuParameters): class SolarPanelBatteryParameters(BaseBatteryParameters):
max_ladeleistung_w: Optional[float] = max_ladeleistung_w_field(5000) max_charge_power_w: Optional[float] = max_charging_power_field()
class EAutoParameters(BaseAkkuParameters): class ElectricVehicleParameters(BaseBatteryParameters):
entlade_effizienz: float = 1.0 """Parameters specific to an electric vehicle (EV)."""
start_soc_prozent: int = start_soc_prozent_field(
discharging_efficiency: float = 1.0
initial_soc_percentage: int = initial_soc_percentage_field(
"An integer representing the current state of charge (SOC) of the battery in percentage." "An integer representing the current state of charge (SOC) of the battery in percentage."
) )
class EAutoResult(BaseModel): class ElectricVehicleResult(BaseModel):
"""This object contains information related to the electric vehicle and its charging and discharging behavior.""" """Result class containing information related to the electric vehicle's charging and discharging behavior."""
charge_array: list[float] = Field( charge_array: list[float] = Field(
description="Indicates for each hour whether the EV is charging (`0` for no charging, `1` for charging)." description="Hourly charging status (0 for no charging, 1 for charging)."
) )
discharge_array: list[int] = Field( discharge_array: list[int] = Field(
description="Indicates for each hour whether the EV is discharging (`0` for no discharging, `1` for discharging)." description="Hourly discharging status (0 for no discharging, 1 for discharging)."
) )
entlade_effizienz: float = Field(description="The discharge efficiency as a float.") discharging_efficiency: float = Field(description="The discharge efficiency as a float..")
hours: int = Field(description="Amount of hours the simulation is done for.") hours: int = Field(description="Number of hours in the simulation.")
kapazitaet_wh: int = Field(description="The capacity of the EVs battery in watt-hours.") capacity_wh: int = Field(description="Capacity of the EVs battery in watt-hours.")
lade_effizienz: float = Field(description="The charging efficiency as a float.") charging_efficiency: float = Field(description="Charging efficiency as a float..")
max_ladeleistung_w: int = Field(description="The maximum charging power of the EV in watts.") max_charge_power_w: int = Field(description="Maximum charging power in watts.")
soc_wh: float = Field( soc_wh: float = Field(
description="The state of charge of the battery in watt-hours at the start of the simulation." description="State of charge of the battery in watt-hours at the start of the simulation."
) )
start_soc_prozent: int = Field( initial_soc_percentage: int = Field(
description="The state of charge of the battery in percentage at the start of the simulation." description="State of charge at the start of the simulation in percentage."
) )
@field_validator( @field_validator("discharge_array", "charge_array", mode="before")
"discharge_array",
"charge_array",
mode="before",
)
def convert_numpy(cls, field: Any) -> Any: def convert_numpy(cls, field: Any) -> Any:
return NumpyEncoder.convert_numpy(field)[0] return NumpyEncoder.convert_numpy(field)[0]
class PVAkku(DeviceBase): class Battery(DeviceBase):
"""Represents a battery device with methods to simulate energy charging and discharging."""
def __init__( def __init__(
self, self,
parameters: Optional[BaseAkkuParameters] = None, parameters: Optional[BaseBatteryParameters] = None,
hours: Optional[int] = 24, hours: Optional[int] = 24,
provider_id: Optional[str] = None, provider_id: Optional[str] = None,
): ):
# Configuration initialisation # Initialize configuration and parameters
self.provider_id = provider_id self.provider_id = provider_id
self.prefix = "<invalid>" self.prefix = "<invalid>"
if self.provider_id == "GenericBattery": if self.provider_id == "GenericBattery":
self.prefix = "battery" self.prefix = "battery"
elif self.provider_id == "GenericBEV": elif self.provider_id == "GenericBEV":
self.prefix = "bev" self.prefix = "bev"
# Parameter initialisiation
self.parameters = parameters self.parameters = parameters
if hours is None: if hours is None:
self.hours = self.total_hours self.hours = self.total_hours # TODO where does that come from?
else: else:
self.hours = hours self.hours = hours
self.initialised = False self.initialised = False
# Run setup if parameters are given, otherwise setup() has to be called later when the config is initialised. # Run setup if parameters are given, otherwise setup() has to be called later when the config is initialised.
if self.parameters is not None: if self.parameters is not None:
self.setup() self.setup()
def setup(self) -> None: def setup(self) -> None:
"""Sets up the battery parameters based on configuration or provided parameters."""
if self.initialised: if self.initialised:
return return
if self.provider_id is not None:
# Setup by configuration if self.provider_id:
# Battery capacity in Wh # Setup from configuration
self.kapazitaet_wh = getattr(self.config, f"{self.prefix}_capacity") self.capacity_wh = getattr(self.config, f"{self.prefix}_capacity")
# Initial state of charge in Wh self.initial_soc_percentage = getattr(self.config, f"{self.prefix}_initial_soc")
self.start_soc_prozent = getattr(self.config, f"{self.prefix}_soc_start") self.hours = self.total_hours # TODO where does that come from?
self.hours = self.total_hours self.charging_efficiency = getattr(self.config, f"{self.prefix}_charging_efficiency")
# Charge and discharge efficiency self.discharging_efficiency = getattr(
self.lade_effizienz = getattr(self.config, f"{self.prefix}_charge_efficiency") self.config, f"{self.prefix}_discharging_efficiency"
self.entlade_effizienz = getattr(self.config, f"{self.prefix}_discharge_efficiency") )
self.max_ladeleistung_w = getattr(self.config, f"{self.prefix}_charge_power_max") self.max_charge_power_w = getattr(self.config, f"{self.prefix}_max_charging_power")
# Only assign for storage battery
if self.provider_id == "GenericBattery": if self.provider_id == "GenericBattery":
self.min_soc_prozent = getattr(self.config, f"{self.prefix}_soc_mint") self.min_soc_percentage = getattr(
self.config,
f"{self.prefix}_soc_min",
)
else: else:
self.min_soc_prozent = 0 self.min_soc_percentage = 0
self.max_soc_prozent = getattr(self.config, f"{self.prefix}_soc_mint")
elif self.parameters is not None: self.max_soc_percentage = getattr(
# Setup by parameters self.config,
# Battery capacity in Wh f"{self.prefix}_soc_max",
self.kapazitaet_wh = self.parameters.kapazitaet_wh )
# Initial state of charge in Wh elif self.parameters:
self.start_soc_prozent = self.parameters.start_soc_prozent # Setup from parameters
# Charge and discharge efficiency self.capacity_wh = self.parameters.capacity_wh
self.lade_effizienz = self.parameters.lade_effizienz self.initial_soc_percentage = self.parameters.initial_soc_percentage
self.entlade_effizienz = self.parameters.entlade_effizienz self.charging_efficiency = self.parameters.charging_efficiency
self.max_ladeleistung_w = self.parameters.max_ladeleistung_w self.discharging_efficiency = self.parameters.discharging_efficiency
self.max_charge_power_w = self.parameters.max_charge_power_w
# Only assign for storage battery # Only assign for storage battery
self.min_soc_prozent = ( self.min_soc_percentage = (
self.parameters.min_soc_prozent self.parameters.min_soc_percentage
if isinstance(self.parameters, PVAkkuParameters) if isinstance(self.parameters, SolarPanelBatteryParameters)
else 0 else 0
) )
self.max_soc_prozent = self.parameters.max_soc_prozent self.max_soc_percentage = self.parameters.max_soc_percentage
else: else:
error_msg = "Parameters and provider ID missing. Can't instantiate." error_msg = "Parameters and provider ID are missing. Cannot instantiate."
logger.error(error_msg) logger.error(error_msg)
raise ValueError(error_msg) raise ValueError(error_msg)
# init # Initialize state of charge
if self.max_ladeleistung_w is None: if self.max_charge_power_w is None:
self.max_ladeleistung_w = self.kapazitaet_wh self.max_charge_power_w = self.capacity_wh # TODO this should not be equal capacity_wh
self.discharge_array = np.full(self.hours, 1) self.discharge_array = np.full(self.hours, 1)
self.charge_array = np.full(self.hours, 1) self.charge_array = np.full(self.hours, 1)
# Calculate start, min and max SoC in Wh self.soc_wh = (self.initial_soc_percentage / 100) * self.capacity_wh
self.soc_wh = (self.start_soc_prozent / 100) * self.kapazitaet_wh self.min_soc_wh = (self.min_soc_percentage / 100) * self.capacity_wh
self.min_soc_wh = (self.min_soc_prozent / 100) * self.kapazitaet_wh self.max_soc_wh = (self.max_soc_percentage / 100) * self.capacity_wh
self.max_soc_wh = (self.max_soc_prozent / 100) * self.kapazitaet_wh
self.initialised = True self.initialised = True
def to_dict(self) -> dict[str, Any]: def to_dict(self) -> dict[str, Any]:
"""Converts the object to a dictionary representation."""
return { return {
"kapazitaet_wh": self.kapazitaet_wh, "capacity_wh": self.capacity_wh,
"start_soc_prozent": self.start_soc_prozent, "initial_soc_percentage": self.initial_soc_percentage,
"soc_wh": self.soc_wh, "soc_wh": self.soc_wh,
"hours": self.hours, "hours": self.hours,
"discharge_array": self.discharge_array, "discharge_array": self.discharge_array,
"charge_array": self.charge_array, "charge_array": self.charge_array,
"lade_effizienz": self.lade_effizienz, "charging_efficiency": self.charging_efficiency,
"entlade_effizienz": self.entlade_effizienz, "discharging_efficiency": self.discharging_efficiency,
"max_ladeleistung_w": self.max_ladeleistung_w, "max_charge_power_w": self.max_charge_power_w,
} }
def reset(self) -> None: def reset(self) -> None:
self.soc_wh = (self.start_soc_prozent / 100) * self.kapazitaet_wh """Resets the battery state to its initial values."""
# Ensure soc_wh is within min and max limits self.soc_wh = (self.initial_soc_percentage / 100) * self.capacity_wh
self.soc_wh = min(max(self.soc_wh, self.min_soc_wh), self.max_soc_wh) self.soc_wh = min(max(self.soc_wh, self.min_soc_wh), self.max_soc_wh)
self.discharge_array = np.full(self.hours, 1) self.discharge_array = np.full(self.hours, 1)
self.charge_array = np.full(self.hours, 1) self.charge_array = np.full(self.hours, 1)
def set_discharge_per_hour(self, discharge_array: np.ndarray) -> None: def set_discharge_per_hour(self, discharge_array: np.ndarray) -> None:
assert len(discharge_array) == self.hours """Sets the discharge values for each hour."""
if len(discharge_array) != self.hours:
raise ValueError(f"Discharge array must have exactly {self.hours} elements.")
self.discharge_array = np.array(discharge_array) self.discharge_array = np.array(discharge_array)
def set_charge_per_hour(self, charge_array: np.ndarray) -> None: def set_charge_per_hour(self, charge_array: np.ndarray) -> None:
assert len(charge_array) == self.hours """Sets the charge values for each hour."""
if len(charge_array) != self.hours:
raise ValueError(f"Charge array must have exactly {self.hours} elements.")
self.charge_array = np.array(charge_array) self.charge_array = np.array(charge_array)
def set_charge_allowed_for_hour(self, charge: float, hour: int) -> None: def set_charge_allowed_for_hour(self, charge: float, hour: int) -> None:
assert hour < self.hours """Sets the charge for a specific hour."""
if hour >= self.hours:
raise ValueError(f"Hour {hour} is out of range. Must be less than {self.hours}.")
self.charge_array[hour] = charge self.charge_array[hour] = charge
def ladezustand_in_prozent(self) -> float: def current_soc_percentage(self) -> float:
return (self.soc_wh / self.kapazitaet_wh) * 100 """Calculates the current state of charge in percentage."""
return (self.soc_wh / self.capacity_wh) * 100
def energie_abgeben(self, wh: float, hour: int) -> tuple[float, float]: def discharge_energy(self, wh: float, hour: int) -> tuple[float, float]:
"""Discharges energy from the battery."""
if self.discharge_array[hour] == 0: if self.discharge_array[hour] == 0:
return 0.0, 0.0 # No energy discharge and no losses return 0.0, 0.0
# Calculate the maximum energy that can be discharged considering min_soc and efficiency max_possible_discharge_wh = (self.soc_wh - self.min_soc_wh) * self.discharging_efficiency
max_possible_discharge_wh = (self.soc_wh - self.min_soc_wh) * self.entlade_effizienz max_possible_discharge_wh = max(max_possible_discharge_wh, 0.0)
max_possible_discharge_wh = max(max_possible_discharge_wh, 0.0) # Ensure non-negative
# Consider the maximum discharge power of the battery max_possible_discharge_wh = min(
max_abgebbar_wh = min(max_possible_discharge_wh, self.max_ladeleistung_w) max_possible_discharge_wh, self.max_charge_power_w
) # TODO make a new cfg variable max_discharge_power_w
# The actually discharged energy cannot exceed requested energy or maximum discharge actual_discharge_wh = min(wh, max_possible_discharge_wh)
tatsaechlich_abgegeben_wh = min(wh, max_abgebbar_wh) actual_withdrawal_wh = (
actual_discharge_wh / self.discharging_efficiency
if self.discharging_efficiency > 0
else 0.0
)
# Calculate the actual amount withdrawn from the battery (before efficiency loss) self.soc_wh -= actual_withdrawal_wh
if self.entlade_effizienz > 0:
tatsaechliche_entnahme_wh = tatsaechlich_abgegeben_wh / self.entlade_effizienz
else:
tatsaechliche_entnahme_wh = 0.0
# Update the state of charge considering the actual withdrawal
self.soc_wh -= tatsaechliche_entnahme_wh
# Ensure soc_wh does not go below min_soc_wh
self.soc_wh = max(self.soc_wh, self.min_soc_wh) self.soc_wh = max(self.soc_wh, self.min_soc_wh)
# Calculate losses due to efficiency losses_wh = actual_withdrawal_wh - actual_discharge_wh
verluste_wh = tatsaechliche_entnahme_wh - tatsaechlich_abgegeben_wh return actual_discharge_wh, losses_wh
# Return the actually discharged energy and the losses def charge_energy(
return tatsaechlich_abgegeben_wh, verluste_wh
def energie_laden(
self, wh: Optional[float], hour: int, relative_power: float = 0.0 self, wh: Optional[float], hour: int, relative_power: float = 0.0
) -> tuple[float, float]: ) -> tuple[float, float]:
"""Charges energy into the battery."""
if hour is not None and self.charge_array[hour] == 0: if hour is not None and self.charge_array[hour] == 0:
return 0.0, 0.0 # Charging not allowed in this hour return 0.0, 0.0 # Charging not allowed in this hour
if relative_power > 0.0: if relative_power > 0.0:
wh = self.max_ladeleistung_w * relative_power wh = self.max_charge_power_w * relative_power
# If no value for wh is given, use the maximum charging power
wh = wh if wh is not None else self.max_ladeleistung_w
# Calculate the maximum energy that can be charged considering max_soc and efficiency wh = wh if wh is not None else self.max_charge_power_w
if self.lade_effizienz > 0:
max_possible_charge_wh = (self.max_soc_wh - self.soc_wh) / self.lade_effizienz
else:
max_possible_charge_wh = 0.0
max_possible_charge_wh = max(max_possible_charge_wh, 0.0) # Ensure non-negative
# The actually charged energy cannot exceed requested energy, charging power, or maximum possible charge max_possible_charge_wh = (
effektive_lademenge = min(wh, max_possible_charge_wh) (self.max_soc_wh - self.soc_wh) / self.charging_efficiency
if self.charging_efficiency > 0
else 0.0
)
max_possible_charge_wh = max(max_possible_charge_wh, 0.0)
# Energy actually stored in the battery effective_charge_wh = min(wh, max_possible_charge_wh)
geladene_menge = effektive_lademenge * self.lade_effizienz charged_wh = effective_charge_wh * self.charging_efficiency
# Update soc_wh self.soc_wh += charged_wh
self.soc_wh += geladene_menge
# Ensure soc_wh does not exceed max_soc_wh
self.soc_wh = min(self.soc_wh, self.max_soc_wh) self.soc_wh = min(self.soc_wh, self.max_soc_wh)
# Calculate losses losses_wh = effective_charge_wh - charged_wh
verluste_wh = effektive_lademenge - geladene_menge return charged_wh, losses_wh
return geladene_menge, verluste_wh
def aktueller_energieinhalt(self) -> float: def current_energy_content(self) -> float:
"""This method returns the current remaining energy considering efficiency. """Returns the current usable energy in the battery."""
usable_energy = (self.soc_wh - self.min_soc_wh) * self.discharging_efficiency
It accounts for both charging and discharging efficiency. return max(usable_energy, 0.0)
"""
# Calculate remaining energy considering discharge efficiency
nutzbare_energie = (self.soc_wh - self.min_soc_wh) * self.entlade_effizienz
return max(nutzbare_energie, 0.0)
if __name__ == "__main__":
# Test battery discharge below min_soc
print("Test: Discharge below min_soc")
akku = PVAkku(
PVAkkuParameters(
kapazitaet_wh=10000,
start_soc_prozent=50,
min_soc_prozent=20,
max_soc_prozent=80,
),
hours=1,
)
akku.reset()
print(f"Initial SoC: {akku.ladezustand_in_prozent()}%")
# Try to discharge 5000 Wh
abgegeben_wh, verlust_wh = akku.energie_abgeben(5000, 0)
print(f"Energy discharged: {abgegeben_wh} Wh, Losses: {verlust_wh} Wh")
print(f"SoC after discharge: {akku.ladezustand_in_prozent()}%")
print(f"Expected min SoC: {akku.min_soc_prozent}%")
# Test battery charge above max_soc
print("\nTest: Charge above max_soc")
akku = PVAkku(
PVAkkuParameters(
kapazitaet_wh=10000,
start_soc_prozent=50,
min_soc_prozent=20,
max_soc_prozent=80,
),
hours=1,
)
akku.reset()
print(f"Initial SoC: {akku.ladezustand_in_prozent()}%")
# Try to charge 5000 Wh
geladen_wh, verlust_wh = akku.energie_laden(5000, 0)
print(f"Energy charged: {geladen_wh} Wh, Losses: {verlust_wh} Wh")
print(f"SoC after charge: {akku.ladezustand_in_prozent()}%")
print(f"Expected max SoC: {akku.max_soc_prozent}%")
# Test charging when battery is at max_soc
print("\nTest: Charging when at max_soc")
akku = PVAkku(
PVAkkuParameters(
kapazitaet_wh=10000,
start_soc_prozent=80,
min_soc_prozent=20,
max_soc_prozent=80,
),
hours=1,
)
akku.reset()
print(f"Initial SoC: {akku.ladezustand_in_prozent()}%")
geladen_wh, verlust_wh = akku.energie_laden(5000, 0)
print(f"Energy charged: {geladen_wh} Wh, Losses: {verlust_wh} Wh")
print(f"SoC after charge: {akku.ladezustand_in_prozent()}%")
# Test discharging when battery is at min_soc
print("\nTest: Discharging when at min_soc")
akku = PVAkku(
PVAkkuParameters(
kapazitaet_wh=10000,
start_soc_prozent=20,
min_soc_prozent=20,
max_soc_prozent=80,
),
hours=1,
)
akku.reset()
print(f"Initial SoC: {akku.ladezustand_in_prozent()}%")
abgegeben_wh, verlust_wh = akku.energie_abgeben(5000, 0)
print(f"Energy discharged: {abgegeben_wh} Wh, Losses: {verlust_wh} Wh")
print(f"SoC after discharge: {akku.ladezustand_in_prozent()}%")

View File

@ -6,7 +6,7 @@ from pydantic import Field, computed_field
from akkudoktoreos.config.configabc import SettingsBaseModel from akkudoktoreos.config.configabc import SettingsBaseModel
from akkudoktoreos.core.coreabc import SingletonMixin from akkudoktoreos.core.coreabc import SingletonMixin
from akkudoktoreos.devices.battery import PVAkku from akkudoktoreos.devices.battery import Battery
from akkudoktoreos.devices.devicesabc import DevicesBase from akkudoktoreos.devices.devicesabc import DevicesBase
from akkudoktoreos.devices.generic import HomeAppliance from akkudoktoreos.devices.generic import HomeAppliance
from akkudoktoreos.devices.inverter import Inverter from akkudoktoreos.devices.inverter import Inverter
@ -25,7 +25,7 @@ class DevicesCommonSettings(SettingsBaseModel):
default=None, description="Id of Battery simulation provider." default=None, description="Id of Battery simulation provider."
) )
battery_capacity: Optional[int] = Field(default=None, description="Battery capacity [Wh].") battery_capacity: Optional[int] = Field(default=None, description="Battery capacity [Wh].")
battery_soc_start: Optional[int] = Field( battery_initial_soc: Optional[int] = Field(
default=None, description="Battery initial state of charge [%]." default=None, description="Battery initial state of charge [%]."
) )
battery_soc_min: Optional[int] = Field( battery_soc_min: Optional[int] = Field(
@ -34,13 +34,13 @@ class DevicesCommonSettings(SettingsBaseModel):
battery_soc_max: Optional[int] = Field( battery_soc_max: Optional[int] = Field(
default=None, description="Battery maximum state of charge [%]." default=None, description="Battery maximum state of charge [%]."
) )
battery_charge_efficiency: Optional[float] = Field( battery_charging_efficiency: Optional[float] = Field(
default=None, description="Battery charging efficiency [%]." default=None, description="Battery charging efficiency [%]."
) )
battery_discharge_efficiency: Optional[float] = Field( battery_discharging_efficiency: Optional[float] = Field(
default=None, description="Battery discharging efficiency [%]." default=None, description="Battery discharging efficiency [%]."
) )
battery_charge_power_max: Optional[int] = Field( battery_max_charging_power: Optional[int] = Field(
default=None, description="Battery maximum charge power [W]." default=None, description="Battery maximum charge power [W]."
) )
@ -52,19 +52,19 @@ class DevicesCommonSettings(SettingsBaseModel):
bev_capacity: Optional[int] = Field( bev_capacity: Optional[int] = Field(
default=None, description="Battery Electric Vehicle capacity [Wh]." default=None, description="Battery Electric Vehicle capacity [Wh]."
) )
bev_soc_start: Optional[int] = Field( bev_initial_soc: Optional[int] = Field(
default=None, description="Battery Electric Vehicle initial state of charge [%]." default=None, description="Battery Electric Vehicle initial state of charge [%]."
) )
bev_soc_max: Optional[int] = Field( bev_soc_max: Optional[int] = Field(
default=None, description="Battery Electric Vehicle maximum state of charge [%]." default=None, description="Battery Electric Vehicle maximum state of charge [%]."
) )
bev_charge_efficiency: Optional[float] = Field( bev_charging_efficiency: Optional[float] = Field(
default=None, description="Battery Electric Vehicle charging efficiency [%]." default=None, description="Battery Electric Vehicle charging efficiency [%]."
) )
bev_discharge_efficiency: Optional[float] = Field( bev_discharging_efficiency: Optional[float] = Field(
default=None, description="Battery Electric Vehicle discharging efficiency [%]." default=None, description="Battery Electric Vehicle discharging efficiency [%]."
) )
bev_charge_power_max: Optional[int] = Field( bev_max_charging_power: Optional[int] = Field(
default=None, description="Battery Electric Vehicle maximum charge power [W]." default=None, description="Battery Electric Vehicle maximum charge power [W]."
) )
@ -159,8 +159,8 @@ class Devices(SingletonMixin, DevicesBase):
# Devices # Devices
# TODO: Make devices class a container of device simulation providers. # TODO: Make devices class a container of device simulation providers.
# Device simulations to be used are then enabled in the configuration. # Device simulations to be used are then enabled in the configuration.
akku: ClassVar[PVAkku] = PVAkku(provider_id="GenericBattery") akku: ClassVar[Battery] = Battery(provider_id="GenericBattery")
eauto: ClassVar[PVAkku] = PVAkku(provider_id="GenericBEV") eauto: ClassVar[Battery] = Battery(provider_id="GenericBEV")
home_appliance: ClassVar[HomeAppliance] = HomeAppliance(provider_id="GenericDishWasher") home_appliance: ClassVar[HomeAppliance] = HomeAppliance(provider_id="GenericDishWasher")
inverter: ClassVar[Inverter] = Inverter(akku=akku, provider_id="GenericInverter") inverter: ClassVar[Inverter] = Inverter(akku=akku, provider_id="GenericInverter")
@ -186,9 +186,9 @@ class Devices(SingletonMixin, DevicesBase):
# Set initial state # Set initial state
simulation_step = to_duration("1 hour") simulation_step = to_duration("1 hour")
if self.akku: if self.akku:
self.akku_soc_pro_stunde[0] = self.akku.ladezustand_in_prozent() self.akku_soc_pro_stunde[0] = self.akku.current_soc_percentage()
if self.eauto: if self.eauto:
self.eauto_soc_pro_stunde[0] = self.eauto.ladezustand_in_prozent() self.eauto_soc_pro_stunde[0] = self.eauto.current_soc_percentage()
# Get predictions for full device simulation time range # Get predictions for full device simulation time range
# gesamtlast[stunde] # gesamtlast[stunde]
@ -232,12 +232,12 @@ class Devices(SingletonMixin, DevicesBase):
# E-Auto handling # E-Auto handling
if self.eauto: if self.eauto:
if self.ev_charge_hours[hour] > 0: if self.ev_charge_hours[hour] > 0:
geladene_menge_eauto, verluste_eauto = self.eauto.energie_laden( geladene_menge_eauto, verluste_eauto = self.eauto.charge_energy(
None, hour, relative_power=self.ev_charge_hours[hour] None, hour, relative_power=self.ev_charge_hours[hour]
) )
consumption += geladene_menge_eauto consumption += geladene_menge_eauto
self.verluste_wh_pro_stunde[stunde_since_now] += verluste_eauto self.verluste_wh_pro_stunde[stunde_since_now] += verluste_eauto
self.eauto_soc_pro_stunde[stunde_since_now] = self.eauto.ladezustand_in_prozent() self.eauto_soc_pro_stunde[stunde_since_now] = self.eauto.current_soc_percentage()
# Process inverter logic # Process inverter logic
grid_export, grid_import, losses, self_consumption = (0.0, 0.0, 0.0, 0.0) grid_export, grid_import, losses, self_consumption = (0.0, 0.0, 0.0, 0.0)
@ -252,10 +252,10 @@ class Devices(SingletonMixin, DevicesBase):
# AC PV Battery Charge # AC PV Battery Charge
if self.akku and self.ac_charge_hours[hour] > 0.0: if self.akku and self.ac_charge_hours[hour] > 0.0:
self.akku.set_charge_allowed_for_hour(1, hour) self.akku.set_charge_allowed_for_hour(1, hour)
geladene_menge, verluste_wh = self.akku.energie_laden( geladene_menge, verluste_wh = self.akku.charge_energy(
None, hour, relative_power=self.ac_charge_hours[hour] None, hour, relative_power=self.ac_charge_hours[hour]
) )
# print(stunde, " ", geladene_menge, " ",self.ac_charge_hours[stunde]," ",self.akku.ladezustand_in_prozent()) # print(stunde, " ", geladene_menge, " ",self.ac_charge_hours[stunde]," ",self.akku.current_soc_percentage())
consumption += geladene_menge consumption += geladene_menge
grid_import += geladene_menge grid_import += geladene_menge
self.verluste_wh_pro_stunde[stunde_since_now] += verluste_wh self.verluste_wh_pro_stunde[stunde_since_now] += verluste_wh
@ -275,7 +275,7 @@ class Devices(SingletonMixin, DevicesBase):
# Akku SOC tracking # Akku SOC tracking
if self.akku: if self.akku:
self.akku_soc_pro_stunde[stunde_since_now] = self.akku.ladezustand_in_prozent() self.akku_soc_pro_stunde[stunde_since_now] = self.akku.current_soc_percentage()
else: else:
self.akku_soc_pro_stunde[stunde_since_now] = 0.0 self.akku_soc_pro_stunde[stunde_since_now] = 0.0

View File

@ -2,7 +2,7 @@ from typing import Optional, Tuple
from pydantic import BaseModel, Field from pydantic import BaseModel, Field
from akkudoktoreos.devices.battery import PVAkku from akkudoktoreos.devices.battery import Battery
from akkudoktoreos.devices.devicesabc import DeviceBase from akkudoktoreos.devices.devicesabc import DeviceBase
from akkudoktoreos.utils.logutil import get_logger from akkudoktoreos.utils.logutil import get_logger
@ -17,7 +17,7 @@ class Inverter(DeviceBase):
def __init__( def __init__(
self, self,
parameters: Optional[InverterParameters] = None, parameters: Optional[InverterParameters] = None,
akku: Optional[PVAkku] = None, akku: Optional[Battery] = None,
provider_id: Optional[str] = None, provider_id: Optional[str] = None,
): ):
# Configuration initialisation # Configuration initialisation
@ -70,7 +70,7 @@ class Inverter(DeviceBase):
remaining_energy = generation - actual_consumption remaining_energy = generation - actual_consumption
# Charge battery with excess energy # Charge battery with excess energy
charged_energy, charging_losses = self.akku.energie_laden(remaining_energy, hour) charged_energy, charging_losses = self.akku.charge_energy(remaining_energy, hour)
losses += charging_losses losses += charging_losses
# Calculate remaining surplus after battery charge # Calculate remaining surplus after battery charge
@ -87,7 +87,7 @@ class Inverter(DeviceBase):
available_ac_power = max(self.max_power_wh - generation, 0) available_ac_power = max(self.max_power_wh - generation, 0)
# Discharge battery to cover shortfall, if possible # Discharge battery to cover shortfall, if possible
battery_discharge, discharge_losses = self.akku.energie_abgeben( battery_discharge, discharge_losses = self.akku.discharge_energy(
min(shortfall, available_ac_power), hour min(shortfall, available_ac_power), hour
) )
losses += discharge_losses losses += discharge_losses

View File

@ -13,10 +13,10 @@ from akkudoktoreos.core.coreabc import (
) )
from akkudoktoreos.core.ems import EnergieManagementSystemParameters, SimulationResult from akkudoktoreos.core.ems import EnergieManagementSystemParameters, SimulationResult
from akkudoktoreos.devices.battery import ( from akkudoktoreos.devices.battery import (
EAutoParameters, Battery,
EAutoResult, ElectricVehicleParameters,
PVAkku, ElectricVehicleResult,
PVAkkuParameters, SolarPanelBatteryParameters,
) )
from akkudoktoreos.devices.generic import HomeAppliance, HomeApplianceParameters from akkudoktoreos.devices.generic import HomeAppliance, HomeApplianceParameters
from akkudoktoreos.devices.inverter import Inverter, InverterParameters from akkudoktoreos.devices.inverter import Inverter, InverterParameters
@ -26,9 +26,9 @@ from akkudoktoreos.visualize import visualisiere_ergebnisse
class OptimizationParameters(BaseModel): class OptimizationParameters(BaseModel):
ems: EnergieManagementSystemParameters ems: EnergieManagementSystemParameters
pv_akku: PVAkkuParameters pv_akku: SolarPanelBatteryParameters
inverter: InverterParameters = InverterParameters() inverter: InverterParameters = InverterParameters()
eauto: Optional[EAutoParameters] eauto: Optional[ElectricVehicleParameters]
dishwasher: Optional[HomeApplianceParameters] = None dishwasher: Optional[HomeApplianceParameters] = None
temperature_forecast: Optional[list[float]] = Field( temperature_forecast: Optional[list[float]] = Field(
default=None, default=None,
@ -68,7 +68,7 @@ class OptimizeResponse(BaseModel):
) )
eautocharge_hours_float: Optional[list[float]] = Field(description="TBD") eautocharge_hours_float: Optional[list[float]] = Field(description="TBD")
result: SimulationResult result: SimulationResult
eauto_obj: Optional[EAutoResult] eauto_obj: Optional[ElectricVehicleResult]
start_solution: Optional[list[float]] = Field( start_solution: Optional[list[float]] = Field(
default=None, default=None,
description="An array of binary values (0 or 1) representing a possible starting solution for the simulation.", description="An array of binary values (0 or 1) representing a possible starting solution for the simulation.",
@ -92,8 +92,8 @@ class OptimizeResponse(BaseModel):
mode="before", mode="before",
) )
def convert_eauto(cls, field: Any) -> Any: def convert_eauto(cls, field: Any) -> Any:
if isinstance(field, PVAkku): if isinstance(field, Battery):
return EAutoResult(**field.to_dict()) return ElectricVehicleResult(**field.to_dict())
return field return field
@ -367,7 +367,7 @@ class optimization_problem(ConfigMixin, DevicesMixin, EnergyManagementSystemMixi
) )
# Penalty for not meeting the minimum SOC (State of Charge) requirement # Penalty for not meeting the minimum SOC (State of Charge) requirement
# if parameters.eauto_min_soc_prozent - ems.eauto.ladezustand_in_prozent() <= 0.0 and self.optimize_ev: # if parameters.eauto_min_soc_prozent - ems.eauto.current_soc_percentage() <= 0.0 and self.optimize_ev:
# gesamtbilanz += sum( # gesamtbilanz += sum(
# self.config.optimization_penalty for ladeleistung in eautocharge_hours_float if ladeleistung != 0.0 # self.config.optimization_penalty for ladeleistung in eautocharge_hours_float if ladeleistung != 0.0
# ) # )
@ -375,7 +375,7 @@ class optimization_problem(ConfigMixin, DevicesMixin, EnergyManagementSystemMixi
individual.extra_data = ( # type: ignore[attr-defined] individual.extra_data = ( # type: ignore[attr-defined]
o["Gesamtbilanz_Euro"], o["Gesamtbilanz_Euro"],
o["Gesamt_Verluste"], o["Gesamt_Verluste"],
parameters.eauto.min_soc_prozent - self.ems.eauto.ladezustand_in_prozent() parameters.eauto.min_soc_percentage - self.ems.eauto.current_soc_percentage()
if parameters.eauto and self.ems.eauto if parameters.eauto and self.ems.eauto
else 0, else 0,
) )
@ -383,16 +383,16 @@ class optimization_problem(ConfigMixin, DevicesMixin, EnergyManagementSystemMixi
# Adjust total balance with battery value and penalties for unmet SOC # Adjust total balance with battery value and penalties for unmet SOC
restwert_akku = ( restwert_akku = (
self.ems.akku.aktueller_energieinhalt() * parameters.ems.preis_euro_pro_wh_akku self.ems.akku.current_energy_content() * parameters.ems.preis_euro_pro_wh_akku
) )
# print(ems.akku.aktueller_energieinhalt()," * ", parameters.ems.preis_euro_pro_wh_akku , " ", restwert_akku, " ", gesamtbilanz) # print(ems.akku.current_energy_content()," * ", parameters.ems.preis_euro_pro_wh_akku , " ", restwert_akku, " ", gesamtbilanz)
gesamtbilanz += -restwert_akku gesamtbilanz += -restwert_akku
# print(gesamtbilanz) # print(gesamtbilanz)
if self.optimize_ev: if self.optimize_ev:
gesamtbilanz += max( gesamtbilanz += max(
0, 0,
( (
parameters.eauto.min_soc_prozent - self.ems.eauto.ladezustand_in_prozent() parameters.eauto.min_soc_percentage - self.ems.eauto.current_soc_percentage()
if parameters.eauto and self.ems.eauto if parameters.eauto and self.ems.eauto
else 0 else 0
) )
@ -458,21 +458,21 @@ class optimization_problem(ConfigMixin, DevicesMixin, EnergyManagementSystemMixi
) )
# Initialize PV and EV batteries # Initialize PV and EV batteries
akku = PVAkku( akku = Battery(
parameters.pv_akku, parameters.pv_akku,
hours=self.config.prediction_hours, hours=self.config.prediction_hours,
) )
akku.set_charge_per_hour(np.full(self.config.prediction_hours, 1)) akku.set_charge_per_hour(np.full(self.config.prediction_hours, 1))
eauto: Optional[PVAkku] = None eauto: Optional[Battery] = None
if parameters.eauto: if parameters.eauto:
eauto = PVAkku( eauto = Battery(
parameters.eauto, parameters.eauto,
hours=self.config.prediction_hours, hours=self.config.prediction_hours,
) )
eauto.set_charge_per_hour(np.full(self.config.prediction_hours, 1)) eauto.set_charge_per_hour(np.full(self.config.prediction_hours, 1))
self.optimize_ev = ( self.optimize_ev = (
parameters.eauto.min_soc_prozent - parameters.eauto.start_soc_prozent >= 0 parameters.eauto.min_soc_percentage - parameters.eauto.initial_soc_percentage >= 0
) )
else: else:
self.optimize_ev = False self.optimize_ev = False

219
tests/test_battery.py Normal file
View File

@ -0,0 +1,219 @@
import numpy as np
import pytest
from akkudoktoreos.devices.battery import Battery, SolarPanelBatteryParameters
@pytest.fixture
def setup_pv_battery():
params = SolarPanelBatteryParameters(
capacity_wh=10000,
initial_soc_percentage=50,
min_soc_percentage=20,
max_soc_percentage=80,
max_charge_power_w=8000,
)
battery = Battery(params, hours=24)
battery.reset()
return battery
def test_initial_state_of_charge(setup_pv_battery):
battery = setup_pv_battery
assert battery.current_soc_percentage() == 50.0, "Initial SoC should be 50%"
def test_battery_discharge_below_min_soc(setup_pv_battery):
battery = setup_pv_battery
discharged_wh, loss_wh = battery.discharge_energy(5000, 0)
# Ensure it discharges energy and stops at the min SOC
assert discharged_wh > 0
print(discharged_wh, loss_wh, battery.current_soc_percentage(), battery.min_soc_percentage)
assert battery.current_soc_percentage() >= 20 # Ensure it's above min_soc_percentage
assert loss_wh >= 0 # Losses should not be negative
assert discharged_wh == 2640.0, "The energy discharged should be limited by min_soc"
def test_battery_charge_above_max_soc(setup_pv_battery):
battery = setup_pv_battery
charged_wh, loss_wh = battery.charge_energy(5000, 0)
# Ensure it charges energy and stops at the max SOC
assert charged_wh > 0
assert battery.current_soc_percentage() <= 80 # Ensure it's below max_soc_percentage
assert loss_wh >= 0 # Losses should not be negative
assert charged_wh == 3000.0, "The energy charged should be limited by max_soc"
def test_battery_charge_when_full(setup_pv_battery):
battery = setup_pv_battery
battery.soc_wh = battery.max_soc_wh # Set battery to full
charged_wh, loss_wh = battery.charge_energy(5000, 0)
# No charging should happen if battery is full
assert charged_wh == 0
assert loss_wh == 0
assert battery.current_soc_percentage() == 80, "SoC should remain at max_soc"
def test_battery_discharge_when_empty(setup_pv_battery):
battery = setup_pv_battery
battery.soc_wh = battery.min_soc_wh # Set battery to minimum SOC
discharged_wh, loss_wh = battery.discharge_energy(5000, 0)
# No discharge should happen if battery is at min SOC
assert discharged_wh == 0
assert loss_wh == 0
assert battery.current_soc_percentage() == 20, "SoC should remain at min_soc"
def test_battery_discharge_exactly_min_soc(setup_pv_battery):
battery = setup_pv_battery
battery.soc_wh = battery.min_soc_wh # Set battery to exactly min SOC
discharged_wh, loss_wh = battery.discharge_energy(1000, 0)
# Battery should not go below the min SOC
assert discharged_wh == 0
assert battery.current_soc_percentage() == 20 # SOC should remain at min_SOC
def test_battery_charge_exactly_max_soc(setup_pv_battery):
battery = setup_pv_battery
battery.soc_wh = battery.max_soc_wh # Set battery to exactly max SOC
charged_wh, loss_wh = battery.charge_energy(1000, 0)
# Battery should not exceed the max SOC
assert charged_wh == 0
assert battery.current_soc_percentage() == 80 # SOC should remain at max_SOC
def test_battery_reset_function(setup_pv_battery):
battery = setup_pv_battery
battery.soc_wh = 8000 # Change the SOC to some value
battery.reset()
# After reset, SOC should be equal to the initial value
assert battery.current_soc_percentage() == battery.initial_soc_percentage
def test_soc_limits(setup_pv_battery):
battery = setup_pv_battery
# Manually set SoC above max limit
battery.soc_wh = battery.max_soc_wh + 1000
battery.soc_wh = min(battery.soc_wh, battery.max_soc_wh)
assert battery.current_soc_percentage() <= 80, "SoC should not exceed max_soc"
# Manually set SoC below min limit
battery.soc_wh = battery.min_soc_wh - 1000
battery.soc_wh = max(battery.soc_wh, battery.min_soc_wh)
assert battery.current_soc_percentage() >= 20, "SoC should not drop below min_soc"
def test_max_charge_power_w(setup_pv_battery):
battery = setup_pv_battery
battery.setup()
assert (
battery.parameters.max_charge_power_w == 8000
), "Default max charge power should be 5000W, We ask for 8000W here"
def test_charge_energy_within_limits(setup_pv_battery):
battery = setup_pv_battery
battery.setup()
initial_soc_wh = battery.soc_wh
charged_wh, losses_wh = battery.charge_energy(wh=4000, hour=1)
assert charged_wh > 0, "Charging should add energy"
assert losses_wh >= 0, "Losses should not be negative"
assert battery.soc_wh > initial_soc_wh, "State of charge should increase after charging"
assert battery.soc_wh <= battery.max_soc_wh, "SOC should not exceed max SOC"
def test_charge_energy_exceeds_capacity(setup_pv_battery):
battery = setup_pv_battery
battery.setup()
initial_soc_wh = battery.soc_wh
# Try to overcharge beyond max capacity
charged_wh, losses_wh = battery.charge_energy(wh=20000, hour=2)
assert (
charged_wh + initial_soc_wh <= battery.max_soc_wh
), "Charging should not exceed max capacity"
assert losses_wh >= 0, "Losses should not be negative"
assert battery.soc_wh == battery.max_soc_wh, "SOC should be at max after overcharge attempt"
def test_charge_energy_not_allowed_hour(setup_pv_battery):
battery = setup_pv_battery
battery.setup()
# Disable charging for all hours
battery.set_charge_per_hour(np.zeros(battery.hours))
charged_wh, losses_wh = battery.charge_energy(wh=4000, hour=3)
assert charged_wh == 0, "No energy should be charged in disallowed hours"
assert losses_wh == 0, "No losses should occur if charging is not allowed"
assert (
battery.soc_wh == (battery.parameters.initial_soc_percentage / 100) * battery.capacity_wh
), "SOC should remain unchanged"
def test_charge_energy_relative_power(setup_pv_battery):
battery = setup_pv_battery
battery.setup()
relative_power = 0.5 # 50% of max charge power
charged_wh, losses_wh = battery.charge_energy(wh=None, hour=4, relative_power=relative_power)
assert charged_wh > 0, "Charging should occur with relative power"
assert losses_wh >= 0, "Losses should not be negative"
assert (
charged_wh <= battery.max_charge_power_w * relative_power
), "Charging should respect relative power limit"
assert battery.soc_wh > 0, "SOC should increase after charging"
@pytest.fixture
def setup_car_battery():
from akkudoktoreos.devices.battery import ElectricVehicleParameters
params = ElectricVehicleParameters(
capacity_wh=40000,
initial_soc_percentage=60,
min_soc_percentage=10,
max_soc_percentage=90,
max_charge_power_w=7000,
)
battery = Battery(params, hours=24)
battery.reset()
return battery
def test_car_and_pv_battery_discharge_and_max_charge_power(setup_pv_battery, setup_car_battery):
pv_battery = setup_pv_battery
car_battery = setup_car_battery
# Test discharge for PV battery
pv_discharged_wh, pv_loss_wh = pv_battery.discharge_energy(3000, 5)
assert pv_discharged_wh > 0, "PV battery should discharge energy"
assert (
pv_battery.current_soc_percentage() >= pv_battery.parameters.min_soc_percentage
), "PV battery SOC should stay above min SOC"
assert (
pv_battery.parameters.max_charge_power_w == 8000
), "PV battery max charge power should remain as defined"
# Test discharge for car battery
car_discharged_wh, car_loss_wh = car_battery.discharge_energy(5000, 10)
assert car_discharged_wh > 0, "Car battery should discharge energy"
assert (
car_battery.current_soc_percentage() >= car_battery.parameters.min_soc_percentage
), "Car battery SOC should stay above min SOC"
assert (
car_battery.parameters.max_charge_power_w == 7000
), "Car battery max charge power should remain as defined"

View File

@ -1,143 +0,0 @@
import unittest
from akkudoktoreos.devices.battery import PVAkku, PVAkkuParameters
class TestPVAkku(unittest.TestCase):
def setUp(self):
# Initializing common parameters for tests
self.kapazitaet_wh = 10000 # 10,000 Wh capacity
self.lade_effizienz = 0.88
self.entlade_effizienz = 0.88
self.min_soc_prozent = 20 # Minimum SoC is 20%
self.max_soc_prozent = 80 # Maximum SoC is 80%
def test_initial_state_of_charge(self):
akku = PVAkku(
PVAkkuParameters(
kapazitaet_wh=self.kapazitaet_wh,
start_soc_prozent=50,
min_soc_prozent=self.min_soc_prozent,
max_soc_prozent=self.max_soc_prozent,
),
hours=1,
)
self.assertEqual(akku.ladezustand_in_prozent(), 50.0, "Initial SoC should be 50%")
def test_discharge_below_min_soc(self):
akku = PVAkku(
PVAkkuParameters(
kapazitaet_wh=self.kapazitaet_wh,
start_soc_prozent=50,
min_soc_prozent=self.min_soc_prozent,
max_soc_prozent=self.max_soc_prozent,
),
hours=1,
)
akku.reset()
# Try to discharge more energy than available above min_soc
abgegeben_wh, verlust_wh = akku.energie_abgeben(5000, 0) # Try to discharge 5000 Wh
expected_soc = self.min_soc_prozent # SoC should not drop below min_soc
self.assertEqual(
akku.ladezustand_in_prozent(),
expected_soc,
"SoC should not drop below min_soc after discharge",
)
self.assertEqual(abgegeben_wh, 2640.0, "The energy discharged should be limited by min_soc")
def test_charge_above_max_soc(self):
akku = PVAkku(
PVAkkuParameters(
kapazitaet_wh=self.kapazitaet_wh,
start_soc_prozent=50,
min_soc_prozent=self.min_soc_prozent,
max_soc_prozent=self.max_soc_prozent,
),
hours=1,
)
akku.reset()
# Try to charge more energy than available up to max_soc
geladen_wh, verlust_wh = akku.energie_laden(5000, 0) # Try to charge 5000 Wh
expected_soc = self.max_soc_prozent # SoC should not exceed max_soc
self.assertEqual(
akku.ladezustand_in_prozent(),
expected_soc,
"SoC should not exceed max_soc after charge",
)
self.assertEqual(geladen_wh, 3000.0, "The energy charged should be limited by max_soc")
def test_charging_at_max_soc(self):
akku = PVAkku(
PVAkkuParameters(
kapazitaet_wh=self.kapazitaet_wh,
start_soc_prozent=80,
min_soc_prozent=self.min_soc_prozent,
max_soc_prozent=self.max_soc_prozent,
),
hours=1,
)
akku.reset()
# Try to charge when SoC is already at max_soc
geladen_wh, verlust_wh = akku.energie_laden(5000, 0)
self.assertEqual(geladen_wh, 0.0, "No energy should be charged when at max_soc")
self.assertEqual(
akku.ladezustand_in_prozent(),
self.max_soc_prozent,
"SoC should remain at max_soc",
)
def test_discharging_at_min_soc(self):
akku = PVAkku(
PVAkkuParameters(
kapazitaet_wh=self.kapazitaet_wh,
start_soc_prozent=20,
min_soc_prozent=self.min_soc_prozent,
max_soc_prozent=self.max_soc_prozent,
),
hours=1,
)
akku.reset()
# Try to discharge when SoC is already at min_soc
abgegeben_wh, verlust_wh = akku.energie_abgeben(5000, 0)
self.assertEqual(abgegeben_wh, 0.0, "No energy should be discharged when at min_soc")
self.assertEqual(
akku.ladezustand_in_prozent(),
self.min_soc_prozent,
"SoC should remain at min_soc",
)
def test_soc_limits(self):
# Test to ensure that SoC never exceeds max_soc or drops below min_soc
akku = PVAkku(
PVAkkuParameters(
kapazitaet_wh=self.kapazitaet_wh,
start_soc_prozent=50,
min_soc_prozent=self.min_soc_prozent,
max_soc_prozent=self.max_soc_prozent,
),
hours=1,
)
akku.reset()
akku.soc_wh = (
self.max_soc_prozent / 100
) * self.kapazitaet_wh + 1000 # Manually set SoC above max limit
akku.soc_wh = min(akku.soc_wh, akku.max_soc_wh)
self.assertLessEqual(
akku.ladezustand_in_prozent(),
self.max_soc_prozent,
"SoC should not exceed max_soc",
)
akku.soc_wh = (
self.min_soc_prozent / 100
) * self.kapazitaet_wh - 1000 # Manually set SoC below min limit
akku.soc_wh = max(akku.soc_wh, akku.min_soc_wh)
self.assertGreaterEqual(
akku.ladezustand_in_prozent(),
self.min_soc_prozent,
"SoC should not drop below min_soc",
)
if __name__ == "__main__":
unittest.main()

View File

@ -8,7 +8,11 @@ from akkudoktoreos.core.ems import (
SimulationResult, SimulationResult,
get_ems, get_ems,
) )
from akkudoktoreos.devices.battery import EAutoParameters, PVAkku, PVAkkuParameters from akkudoktoreos.devices.battery import (
Battery,
ElectricVehicleParameters,
SolarPanelBatteryParameters,
)
from akkudoktoreos.devices.generic import HomeAppliance, HomeApplianceParameters from akkudoktoreos.devices.generic import HomeAppliance, HomeApplianceParameters
from akkudoktoreos.devices.inverter import Inverter, InverterParameters from akkudoktoreos.devices.inverter import Inverter, InverterParameters
@ -25,8 +29,10 @@ def create_ems_instance() -> EnergieManagementSystem:
assert config_eos.prediction_hours is not None assert config_eos.prediction_hours is not None
# Initialize the battery and the inverter # Initialize the battery and the inverter
akku = PVAkku( akku = Battery(
PVAkkuParameters(kapazitaet_wh=5000, start_soc_prozent=80, min_soc_prozent=10), SolarPanelBatteryParameters(
capacity_wh=5000, initial_soc_percentage=80, min_soc_percentage=10
),
hours=config_eos.prediction_hours, hours=config_eos.prediction_hours,
) )
akku.reset() akku.reset()
@ -43,8 +49,10 @@ def create_ems_instance() -> EnergieManagementSystem:
home_appliance.set_starting_time(2) home_appliance.set_starting_time(2)
# Example initialization of electric car battery # Example initialization of electric car battery
eauto = PVAkku( eauto = Battery(
EAutoParameters(kapazitaet_wh=26400, start_soc_prozent=10, min_soc_prozent=10), ElectricVehicleParameters(
capacity_wh=26400, initial_soc_percentage=10, min_soc_percentage=10
),
hours=config_eos.prediction_hours, hours=config_eos.prediction_hours,
) )
eauto.set_charge_per_hour(np.full(config_eos.prediction_hours, 1)) eauto.set_charge_per_hour(np.full(config_eos.prediction_hours, 1))

View File

@ -7,7 +7,11 @@ from akkudoktoreos.core.ems import (
EnergieManagementSystemParameters, EnergieManagementSystemParameters,
get_ems, get_ems,
) )
from akkudoktoreos.devices.battery import EAutoParameters, PVAkku, PVAkkuParameters from akkudoktoreos.devices.battery import (
Battery,
ElectricVehicleParameters,
SolarPanelBatteryParameters,
)
from akkudoktoreos.devices.generic import HomeAppliance, HomeApplianceParameters from akkudoktoreos.devices.generic import HomeAppliance, HomeApplianceParameters
from akkudoktoreos.devices.inverter import Inverter, InverterParameters from akkudoktoreos.devices.inverter import Inverter, InverterParameters
@ -24,8 +28,10 @@ def create_ems_instance() -> EnergieManagementSystem:
assert config_eos.prediction_hours is not None assert config_eos.prediction_hours is not None
# Initialize the battery and the inverter # Initialize the battery and the inverter
akku = PVAkku( akku = Battery(
PVAkkuParameters(kapazitaet_wh=5000, start_soc_prozent=80, min_soc_prozent=10), SolarPanelBatteryParameters(
capacity_wh=5000, initial_soc_percentage=80, min_soc_percentage=10
),
hours=config_eos.prediction_hours, hours=config_eos.prediction_hours,
) )
akku.reset() akku.reset()
@ -42,8 +48,10 @@ def create_ems_instance() -> EnergieManagementSystem:
home_appliance.set_starting_time(2) home_appliance.set_starting_time(2)
# Example initialization of electric car battery # Example initialization of electric car battery
eauto = PVAkku( eauto = Battery(
EAutoParameters(kapazitaet_wh=26400, start_soc_prozent=100, min_soc_prozent=100), ElectricVehicleParameters(
capacity_wh=26400, initial_soc_percentage=100, min_soc_percentage=100
),
hours=config_eos.prediction_hours, hours=config_eos.prediction_hours,
) )

View File

@ -8,8 +8,8 @@ from akkudoktoreos.devices.inverter import Inverter, InverterParameters
@pytest.fixture @pytest.fixture
def mock_battery(): def mock_battery():
mock_battery = Mock() mock_battery = Mock()
mock_battery.energie_laden = Mock(return_value=(0.0, 0.0)) mock_battery.charge_energy = Mock(return_value=(0.0, 0.0))
mock_battery.energie_abgeben = Mock(return_value=(0.0, 0.0)) mock_battery.discharge_energy = Mock(return_value=(0.0, 0.0))
return mock_battery return mock_battery
@ -20,7 +20,7 @@ def inverter(mock_battery):
def test_process_energy_excess_generation(inverter, mock_battery): def test_process_energy_excess_generation(inverter, mock_battery):
# Battery charges 100 Wh with 10 Wh loss # Battery charges 100 Wh with 10 Wh loss
mock_battery.energie_laden.return_value = (100.0, 10.0) mock_battery.charge_energy.return_value = (100.0, 10.0)
generation = 600.0 generation = 600.0
consumption = 200.0 consumption = 200.0
hour = 12 hour = 12
@ -33,7 +33,7 @@ def test_process_energy_excess_generation(inverter, mock_battery):
assert grid_import == 0.0 # No grid draw assert grid_import == 0.0 # No grid draw
assert losses == 10.0 # Battery charging losses assert losses == 10.0 # Battery charging losses
assert self_consumption == 200.0 # All consumption is met assert self_consumption == 200.0 # All consumption is met
mock_battery.energie_laden.assert_called_once_with(400.0, hour) mock_battery.charge_energy.assert_called_once_with(400.0, hour)
def test_process_energy_generation_equals_consumption(inverter, mock_battery): def test_process_energy_generation_equals_consumption(inverter, mock_battery):
@ -50,12 +50,12 @@ def test_process_energy_generation_equals_consumption(inverter, mock_battery):
assert losses == 0.0 # No losses assert losses == 0.0 # No losses
assert self_consumption == 300.0 # All consumption is met with generation assert self_consumption == 300.0 # All consumption is met with generation
mock_battery.energie_laden.assert_called_once_with(0.0, hour) mock_battery.charge_energy.assert_called_once_with(0.0, hour)
def test_process_energy_battery_discharges(inverter, mock_battery): def test_process_energy_battery_discharges(inverter, mock_battery):
# Battery discharges 100 Wh with 10 Wh loss already accounted for in the discharge # Battery discharges 100 Wh with 10 Wh loss already accounted for in the discharge
mock_battery.energie_abgeben.return_value = (100.0, 10.0) mock_battery.discharge_energy.return_value = (100.0, 10.0)
generation = 100.0 generation = 100.0
consumption = 250.0 consumption = 250.0
hour = 12 hour = 12
@ -70,12 +70,12 @@ def test_process_energy_battery_discharges(inverter, mock_battery):
) # Grid supplies remaining shortfall after battery discharge ) # Grid supplies remaining shortfall after battery discharge
assert losses == 10.0 # Discharge losses assert losses == 10.0 # Discharge losses
assert self_consumption == 200.0 # Generation + battery discharge assert self_consumption == 200.0 # Generation + battery discharge
mock_battery.energie_abgeben.assert_called_once_with(150.0, hour) mock_battery.discharge_energy.assert_called_once_with(150.0, hour)
def test_process_energy_battery_empty(inverter, mock_battery): def test_process_energy_battery_empty(inverter, mock_battery):
# Battery is empty, so no energy can be discharged # Battery is empty, so no energy can be discharged
mock_battery.energie_abgeben.return_value = (0.0, 0.0) mock_battery.discharge_energy.return_value = (0.0, 0.0)
generation = 100.0 generation = 100.0
consumption = 300.0 consumption = 300.0
hour = 12 hour = 12
@ -88,12 +88,12 @@ def test_process_energy_battery_empty(inverter, mock_battery):
assert grid_import == pytest.approx(200.0, rel=1e-2) # Grid has to cover the full shortfall assert grid_import == pytest.approx(200.0, rel=1e-2) # Grid has to cover the full shortfall
assert losses == 0.0 # No losses as the battery didn't discharge assert losses == 0.0 # No losses as the battery didn't discharge
assert self_consumption == 100.0 # Only generation is consumed assert self_consumption == 100.0 # Only generation is consumed
mock_battery.energie_abgeben.assert_called_once_with(200.0, hour) mock_battery.discharge_energy.assert_called_once_with(200.0, hour)
def test_process_energy_battery_full_at_start(inverter, mock_battery): def test_process_energy_battery_full_at_start(inverter, mock_battery):
# Battery is full, so no charging happens # Battery is full, so no charging happens
mock_battery.energie_laden.return_value = (0.0, 0.0) mock_battery.charge_energy.return_value = (0.0, 0.0)
generation = 500.0 generation = 500.0
consumption = 200.0 consumption = 200.0
hour = 12 hour = 12
@ -108,12 +108,12 @@ def test_process_energy_battery_full_at_start(inverter, mock_battery):
assert grid_import == 0.0 # No grid draw assert grid_import == 0.0 # No grid draw
assert losses == 0.0 # No losses assert losses == 0.0 # No losses
assert self_consumption == 200.0 # Only consumption is met assert self_consumption == 200.0 # Only consumption is met
mock_battery.energie_laden.assert_called_once_with(300.0, hour) mock_battery.charge_energy.assert_called_once_with(300.0, hour)
def test_process_energy_insufficient_generation_no_battery(inverter, mock_battery): def test_process_energy_insufficient_generation_no_battery(inverter, mock_battery):
# Insufficient generation and no battery discharge # Insufficient generation and no battery discharge
mock_battery.energie_abgeben.return_value = (0.0, 0.0) mock_battery.discharge_energy.return_value = (0.0, 0.0)
generation = 100.0 generation = 100.0
consumption = 500.0 consumption = 500.0
hour = 12 hour = 12
@ -126,12 +126,12 @@ def test_process_energy_insufficient_generation_no_battery(inverter, mock_batter
assert grid_import == pytest.approx(400.0, rel=1e-2) # Grid supplies the shortfall assert grid_import == pytest.approx(400.0, rel=1e-2) # Grid supplies the shortfall
assert losses == 0.0 # No losses assert losses == 0.0 # No losses
assert self_consumption == 100.0 # Only generation is consumed assert self_consumption == 100.0 # Only generation is consumed
mock_battery.energie_abgeben.assert_called_once_with(400.0, hour) mock_battery.discharge_energy.assert_called_once_with(400.0, hour)
def test_process_energy_insufficient_generation_battery_assists(inverter, mock_battery): def test_process_energy_insufficient_generation_battery_assists(inverter, mock_battery):
# Battery assists with some discharge to cover the shortfall # Battery assists with some discharge to cover the shortfall
mock_battery.energie_abgeben.return_value = ( mock_battery.discharge_energy.return_value = (
50.0, 50.0,
5.0, 5.0,
) # Battery discharges 50 Wh with 5 Wh loss ) # Battery discharges 50 Wh with 5 Wh loss
@ -149,12 +149,12 @@ def test_process_energy_insufficient_generation_battery_assists(inverter, mock_b
) # Grid supplies the remaining shortfall after battery discharge ) # Grid supplies the remaining shortfall after battery discharge
assert losses == 5.0 # Discharge losses assert losses == 5.0 # Discharge losses
assert self_consumption == 250.0 # Generation + battery discharge assert self_consumption == 250.0 # Generation + battery discharge
mock_battery.energie_abgeben.assert_called_once_with(200.0, hour) mock_battery.discharge_energy.assert_called_once_with(200.0, hour)
def test_process_energy_zero_generation(inverter, mock_battery): def test_process_energy_zero_generation(inverter, mock_battery):
# Zero generation, full reliance on battery and grid # Zero generation, full reliance on battery and grid
mock_battery.energie_abgeben.return_value = ( mock_battery.discharge_energy.return_value = (
100.0, 100.0,
5.0, 5.0,
) # Battery discharges 100 Wh with 5 Wh loss ) # Battery discharges 100 Wh with 5 Wh loss
@ -170,12 +170,12 @@ def test_process_energy_zero_generation(inverter, mock_battery):
assert grid_import == pytest.approx(200.0, rel=1e-2) # Grid supplies the remaining shortfall assert grid_import == pytest.approx(200.0, rel=1e-2) # Grid supplies the remaining shortfall
assert losses == 5.0 # Discharge losses assert losses == 5.0 # Discharge losses
assert self_consumption == 100.0 # Only battery discharge is consumed assert self_consumption == 100.0 # Only battery discharge is consumed
mock_battery.energie_abgeben.assert_called_once_with(300.0, hour) mock_battery.discharge_energy.assert_called_once_with(300.0, hour)
def test_process_energy_zero_consumption(inverter, mock_battery): def test_process_energy_zero_consumption(inverter, mock_battery):
# Generation exceeds consumption, but consumption is zero # Generation exceeds consumption, but consumption is zero
mock_battery.energie_laden.return_value = (100.0, 10.0) mock_battery.charge_energy.return_value = (100.0, 10.0)
generation = 500.0 generation = 500.0
consumption = 0.0 consumption = 0.0
hour = 12 hour = 12
@ -188,7 +188,7 @@ def test_process_energy_zero_consumption(inverter, mock_battery):
assert grid_import == 0.0 # No grid draw as no consumption assert grid_import == 0.0 # No grid draw as no consumption
assert losses == 10.0 # Charging losses assert losses == 10.0 # Charging losses
assert self_consumption == 0.0 # Zero consumption assert self_consumption == 0.0 # Zero consumption
mock_battery.energie_laden.assert_called_once_with(500.0, hour) mock_battery.charge_energy.assert_called_once_with(500.0, hour)
def test_process_energy_zero_generation_zero_consumption(inverter, mock_battery): def test_process_energy_zero_generation_zero_consumption(inverter, mock_battery):
@ -207,7 +207,7 @@ def test_process_energy_zero_generation_zero_consumption(inverter, mock_battery)
def test_process_energy_partial_battery_discharge(inverter, mock_battery): def test_process_energy_partial_battery_discharge(inverter, mock_battery):
mock_battery.energie_abgeben.return_value = (50.0, 5.0) mock_battery.discharge_energy.return_value = (50.0, 5.0)
generation = 200.0 generation = 200.0
consumption = 400.0 consumption = 400.0
hour = 12 hour = 12
@ -226,7 +226,7 @@ def test_process_energy_partial_battery_discharge(inverter, mock_battery):
def test_process_energy_consumption_exceeds_max_no_battery(inverter, mock_battery): def test_process_energy_consumption_exceeds_max_no_battery(inverter, mock_battery):
# Battery is empty, and consumption is much higher than the inverter's max power # Battery is empty, and consumption is much higher than the inverter's max power
mock_battery.energie_abgeben.return_value = (0.0, 0.0) mock_battery.discharge_energy.return_value = (0.0, 0.0)
generation = 100.0 generation = 100.0
consumption = 1000.0 # Exceeds the inverter's max power consumption = 1000.0 # Exceeds the inverter's max power
hour = 12 hour = 12
@ -239,12 +239,12 @@ def test_process_energy_consumption_exceeds_max_no_battery(inverter, mock_batter
assert grid_import == pytest.approx(900.0, rel=1e-2) # Grid covers the remaining shortfall assert grid_import == pytest.approx(900.0, rel=1e-2) # Grid covers the remaining shortfall
assert losses == 0.0 # No losses as the battery didnt assist assert losses == 0.0 # No losses as the battery didnt assist
assert self_consumption == 100.0 # Only the generation is consumed, maxing out the inverter assert self_consumption == 100.0 # Only the generation is consumed, maxing out the inverter
mock_battery.energie_abgeben.assert_called_once_with(400.0, hour) mock_battery.discharge_energy.assert_called_once_with(400.0, hour)
def test_process_energy_zero_generation_full_battery_high_consumption(inverter, mock_battery): def test_process_energy_zero_generation_full_battery_high_consumption(inverter, mock_battery):
# Full battery, no generation, and high consumption # Full battery, no generation, and high consumption
mock_battery.energie_abgeben.return_value = (500.0, 10.0) mock_battery.discharge_energy.return_value = (500.0, 10.0)
generation = 0.0 generation = 0.0
consumption = 600.0 consumption = 600.0
hour = 12 hour = 12
@ -259,4 +259,4 @@ def test_process_energy_zero_generation_full_battery_high_consumption(inverter,
) # Grid covers remaining shortfall after battery discharge ) # Grid covers remaining shortfall after battery discharge
assert losses == 10.0 # Battery discharge losses assert losses == 10.0 # Battery discharge losses
assert self_consumption == 500.0 # Battery fully discharges to meet consumption assert self_consumption == 500.0 # Battery fully discharges to meet consumption
mock_battery.energie_abgeben.assert_called_once_with(500.0, hour) mock_battery.discharge_energy.assert_called_once_with(500.0, hour)

View File

@ -26,21 +26,21 @@
] ]
}, },
"pv_akku": { "pv_akku": {
"kapazitaet_wh": 26400, "capacity_wh": 26400,
"max_ladeleistung_w": 5000, "max_charge_power_w": 5000,
"start_soc_prozent": 80, "initial_soc_percentage": 80,
"min_soc_prozent": 15 "min_soc_percentage": 15
}, },
"inverter": { "wechselrichter": {
"max_power_wh": 10000 "max_leistung_wh": 10000
}, },
"eauto": { "eauto": {
"kapazitaet_wh": 60000, "capacity_wh": 60000,
"lade_effizienz": 0.95, "charging_efficiency": 0.95,
"entlade_effizienz": 1.0, "discharging_efficiency": 1.0,
"max_ladeleistung_w": 11040, "max_charge_power_w": 11040,
"start_soc_prozent": 54, "initial_soc_percentage": 54,
"min_soc_prozent": 0 "min_soc_percentage": 0
}, },
"temperature_forecast": [ "temperature_forecast": [
18.3, 17.8, 16.9, 16.2, 15.6, 15.1, 14.6, 14.2, 14.3, 14.8, 15.7, 16.7, 17.4, 18.3, 17.8, 16.9, 16.2, 15.6, 15.1, 14.6, 14.2, 14.3, 14.8, 15.7, 16.7, 17.4,

View File

@ -26,16 +26,16 @@
] ]
}, },
"pv_akku": { "pv_akku": {
"kapazitaet_wh": 26400, "capacity_wh": 26400,
"start_soc_prozent": 80, "initial_soc_percentage": 80,
"min_soc_prozent": 15 "min_soc_percentage": 15
}, },
"eauto": { "eauto": {
"kapazitaet_wh": 60000, "capacity_wh": 60000,
"lade_effizienz": 0.95, "charging_efficiency": 0.95,
"max_ladeleistung_w": 11040, "max_charge_power_w": 11040,
"start_soc_prozent": 5, "initial_soc_percentage": 5,
"min_soc_prozent": 80 "min_soc_percentage": 80
}, },
"dishwasher" :{ "dishwasher" :{
"consumption_wh": 5000, "consumption_wh": 5000,

View File

@ -617,13 +617,13 @@
1, 1,
1 1
], ],
"entlade_effizienz": 1.0, "discharging_efficiency": 1.0,
"hours": 48, "hours": 48,
"kapazitaet_wh": 60000, "capacity_wh": 60000,
"lade_effizienz": 0.95, "charging_efficiency": 0.95,
"max_ladeleistung_w": 11040, "max_charge_power_w": 11040,
"soc_wh": 32400.000000000004, "soc_wh": 32400.000000000004,
"start_soc_prozent": 54 "initial_soc_percentage": 54
}, },
"start_solution": [ "start_solution": [
18.0, 18.0,
@ -676,4 +676,4 @@
10.0 10.0
], ],
"washingstart": null "washingstart": null
} }

View File

@ -666,13 +666,13 @@
1, 1,
1 1
], ],
"entlade_effizienz": 1.0, "discharging_efficiency": 1.0,
"hours": 48, "hours": 48,
"kapazitaet_wh": 60000, "capacity_wh": 60000,
"lade_effizienz": 0.95, "charging_efficiency": 0.95,
"max_ladeleistung_w": 11040, "max_charge_power_w": 11040,
"soc_wh": 60000.0, "soc_wh": 60000.0,
"start_soc_prozent": 5 "initial_soc_percentage": 5
}, },
"start_solution": [ "start_solution": [
0.0, 0.0,
@ -774,4 +774,4 @@
14.0 14.0
], ],
"washingstart": 14 "washingstart": 14
} }

View File

@ -666,13 +666,13 @@
1, 1,
1 1
], ],
"entlade_effizienz": 1.0, "discharging_efficiency": 1.0,
"hours": 48, "hours": 48,
"kapazitaet_wh": 60000, "capacity_wh": 60000,
"lade_effizienz": 0.95, "charging_efficiency": 0.95,
"max_ladeleistung_w": 11040, "max_charge_power_w": 11040,
"soc_wh": 60000.0, "soc_wh": 60000.0,
"start_soc_prozent": 5 "initial_soc_percentage": 5
}, },
"start_solution": [ "start_solution": [
12.0, 12.0,
@ -774,4 +774,4 @@
14.0 14.0
], ],
"washingstart": 14 "washingstart": 14
} }