10 Commits

Author SHA1 Message Date
Normann
eb0f49310c Update README.md 2025-03-23 22:23:20 +01:00
Normann
9c961d886c Update pyproject.toml 2025-03-23 22:22:56 +01:00
Normann
82d633c9b0 Update requirements-dev.txt 2025-03-23 22:22:37 +01:00
Normann
d86b4c089a Update requirements 2025-03-23 22:20:41 +01:00
Bobby Noelte
2a5879c177 Add load figure to demo page. (#469)
Signed-off-by: Bobby Noelte <b0661n0e17e@gmail.com>
2025-03-02 10:48:34 +01:00
Normann
a7d58eed9a pre-commit update and ignore changes (#461)
* pre-commit autoupdate
* type: ignore changes
* [attr-defined,unused-ignore] usage
2025-02-24 10:00:09 +01:00
Bobby Noelte
1020a46435 Add Markdown linter
Add Markdown linter (pymarkdown) to pre-commit.
Adapt current markdown files to fulfill linter rules.

Signed-off-by: Bobby Noelte <b0661n0e17e@gmail.com>
2025-02-18 10:26:38 +01:00
Dennis
8258b1cca1 EOF issue in "optimize" documentation 2025-02-18 10:26:38 +01:00
Dennis
afbe50c388 Initial "optimize" documentation 2025-02-18 10:26:38 +01:00
Bobby Noelte
c8cad0f277 Fix BrightSky weather prediction
- Get weather data with fully specified end_date datetime argument to not miss data.
- Make preciptable water records generation robust against missing temperature
  or humidity values.

Signed-off-by: Bobby Noelte <b0661n0e17e@gmail.com>
2025-02-18 07:04:54 +01:00
24 changed files with 2084 additions and 9702 deletions

View File

@@ -33,3 +33,12 @@ repos:
- "pandas-stubs==2.2.3.241009" - "pandas-stubs==2.2.3.241009"
- "numpy==2.1.3" - "numpy==2.1.3"
pass_filenames: false pass_filenames: false
- repo: https://github.com/jackdewinter/pymarkdown
rev: main
hooks:
- id: pymarkdown
files: ^docs/
exclude: ^docs/_generated
args:
- --config=docs/pymarkdown.json
- scan

View File

@@ -10,7 +10,7 @@ See [CONTRIBUTING.md](CONTRIBUTING.md).
## Installation ## Installation
The project requires Python 3.10 or newer. Official docker images can be found at [akkudoktor/eos](https://hub.docker.com/r/akkudoktor/eos). The project requires Python 3.11 or newer. Official docker images can be found at [akkudoktor/eos](https://hub.docker.com/r/akkudoktor/eos).
Following sections describe how to locally start the EOS server on `http://localhost:8503`. Following sections describe how to locally start the EOS server on `http://localhost:8503`.

File diff suppressed because one or more lines are too long

BIN
docs/_static/optimization_timeframes.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 664 KiB

View File

@@ -20,17 +20,22 @@ EOS Architecture
### Configuration ### Configuration
The configuration controls all aspects of EOS: optimization, prediction, measurement, and energy management. The configuration controls all aspects of EOS: optimization, prediction, measurement, and energy
management.
### Energy Management ### Energy Management
Energy management is the overall process to provide planning data for scheduling the different devices in your system in an optimal way. Energy management cares for the update of predictions and the optimization of the planning based on the simulated behavior of the devices. The planning is on the hour. Sub-hour energy management is left Energy management is the overall process to provide planning data for scheduling the different
devices in your system in an optimal way. Energy management cares for the update of predictions and
the optimization of the planning based on the simulated behavior of the devices. The planning is on
the hour. Sub-hour energy management is left
### Optimization ### Optimization
### Device Simulations ### Device Simulations
Device simulations simulate devices' behavior based on internal logic and predicted data. They provide the data needed for optimization. Device simulations simulate devices' behavior based on internal logic and predicted data. They
provide the data needed for optimization.
### Predictions ### Predictions
@@ -38,7 +43,8 @@ Predictions provide predicted future data to be used by the optimization.
### Measurements ### Measurements
Measurements are utilized to refine predictions using real data from your system, thereby enhancing accuracy. Measurements are utilized to refine predictions using real data from your system, thereby enhancing
accuracy.
### EOS Server ### EOS Server

View File

@@ -31,10 +31,10 @@ Use endpoint `POST /v1/config/reset` to reset the configuration to the values in
The configuration sources and their priorities are as follows: The configuration sources and their priorities are as follows:
1. **Runtime Config Updates**: Provided during runtime by the REST interface 1. `Settings`: Provided during runtime by the REST interface
2. **Environment Variables**: Defined at startup of the REST server and during runtime 2. `Environment Variables`: Defined at startup of the REST server and during runtime
3. **EOS Configuration File**: Read at startup of the REST server and on request 3. `EOS Configuration File`: Read at startup of the REST server and on request
4. **Default Values** 4. `Default Values`
### Runtime Config Updates ### Runtime Config Updates

View File

@@ -17,18 +17,17 @@ APIs, and online services in creative and practical ways.
Andreas Schmitz uses [Node-RED](https://nodered.org/) as part of his home automation setup. Andreas Schmitz uses [Node-RED](https://nodered.org/) as part of his home automation setup.
### Resources ### Node-Red Resources
- [Installation Guide (German)](https://meintechblog.de/2024/09/05/andreas-schmitz-joerg-installiert-mein-energieoptimierungssystem/) — A detailed guide on integrating an early version of EOS with - [Installation Guide (German)](https://meintechblog.de/2024/09/05/andreas-schmitz-joerg-installiert-mein-energieoptimierungssystem/)
`Node-RED`. \— A detailed guide on integrating an early version of EOS with `Node-RED`.
## Home Assistant ## Home Assistant
[Home Assistant](https://www.home-assistant.io/) is an open-source home automation platform that [Home Assistant](https://www.home-assistant.io/) is an open-source home automation platform that
emphasizes local control and user privacy. emphasizes local control and user privacy.
### Resources ### Home Assistant Resources
- Duetting's [EOS Home Assistant Addon](https://github.com/Duetting/ha_eos_addon) — Additional - Duetting's [EOS Home Assistant Addon](https://github.com/Duetting/ha_eos_addon) — Additional
details can be found in this details can be found in this [discussion thread](https://github.com/Akkudoktor-EOS/EOS/discussions/294).
[discussion thread](https://github.com/Akkudoktor-EOS/EOS/discussions/294).

View File

@@ -5,9 +5,9 @@
Measurements are utilized to refine predictions using real data from your system, thereby enhancing Measurements are utilized to refine predictions using real data from your system, thereby enhancing
accuracy. accuracy.
- **Household Load Measurement** - Household Load Measurement
- **Grid Export Measurement** - Grid Export Measurement
- **Grid Import Measurement** - Grid Import Measurement
## Storing Measurements ## Storing Measurements

View File

@@ -2,7 +2,199 @@
# Optimization # Optimization
:::{admonition} Todo ## Introduction
:class: note
Describe optimization. The `POST /optimize` API endpoint optimizes your energy management system based on various inputs
::: including electricity prices, battery storage capacity, PV forecast, and temperature data.
## Input Payload
### Sample Request
```json
{
"ems": {
"preis_euro_pro_wh_akku": 0.0007,
"einspeiseverguetung_euro_pro_wh": 0.00007,
"gesamtlast": [500, 500, ..., 500, 500],
"pv_prognose_wh": [300, 0, 0, ..., 2160, 1840],
"strompreis_euro_pro_wh": [0.0003784, 0.0003868, ..., 0.00034102, 0.00033709]
},
"pv_akku": {
"capacity_wh": 12000,
"charging_efficiency": 0.92,
"discharging_efficiency": 0.92,
"max_charge_power_w": 5700,
"initial_soc_percentage": 66,
"min_soc_percentage": 5,
"max_soc_percentage": 100
},
"inverter": {
"max_power_wh": 15500
},
"eauto": {
"capacity_wh": 64000,
"charging_efficiency": 0.88,
"discharging_efficiency": 0.88,
"max_charge_power_w": 11040,
"initial_soc_percentage": 98,
"min_soc_percentage": 60,
"max_soc_percentage": 100
},
"temperature_forecast": [18.3, 18, ..., 20.16, 19.84],
"start_solution": null
}
```
## Input Parameters
### Energy Management System (EMS)
#### Battery Cost (`preis_euro_pro_wh_akku`)
- Unit: €/Wh
- Purpose: Represents the residual value of energy stored in the battery
- Impact: Lower values encourage battery depletion, higher values preserve charge at the end of the simulation.
#### Feed-in Tariff (`einspeiseverguetung_euro_pro_wh`)
- Unit: €/Wh
- Purpose: Compensation received for feeding excess energy back to the grid
#### Total Load Forecast (`gesamtlast`)
- Unit: W
- Time Range: 48 hours (00:00 today to 23:00 tomorrow)
- Format: Array of hourly values
- Note: Exclude optimizable loads (EV charging, battery charging, etc.)
##### Data Sources
1. Standard Load Profile: `GET /v1/prediction/list?key=load_mean` for a standard load profile based
on your yearly consumption.
2. Adjusted Load Profile: `GET /v1/prediction/list?key=load_mean_adjusted` for a combination of a
standard load profile based on your yearly consumption incl. data from last 48h.
#### PV Generation Forecast (`pv_prognose_wh`)
- Unit: W
- Time Range: 48 hours (00:00 today to 23:00 tomorrow)
- Format: Array of hourly values
- Data Source: `GET /v1/prediction/series?key=pvforecast_ac_power`
#### Electricity Price Forecast (`strompreis_euro_pro_wh`)
- Unit: €/Wh
- Time Range: 48 hours (00:00 today to 23:00 tomorrow)
- Format: Array of hourly values
- Data Source: `GET /v1/prediction/list?key=elecprice_marketprice_wh`
Verify prices against your local tariffs.
### Battery Storage System
#### Configuration
- `capacity_wh`: Total battery capacity in Wh
- `charging_efficiency`: Charging efficiency (0-1)
- `discharging_efficiency`: Discharging efficiency (0-1)
- `max_charge_power_w`: Maximum charging power in W
#### State of Charge (SoC)
- `initial_soc_percentage`: Current battery level (%)
- `min_soc_percentage`: Minimum allowed SoC (%)
- `max_soc_percentage`: Maximum allowed SoC (%)
### Inverter
- `max_power_wh`: Maximum inverter power in Wh
### Electric Vehicle (EV)
- `capacity_wh`: Battery capacity in Wh
- `charging_efficiency`: Charging efficiency (0-1)
- `discharging_efficiency`: Discharging efficiency (0-1)
- `max_charge_power_w`: Maximum charging power in W
- `initial_soc_percentage`: Current charge level (%)
- `min_soc_percentage`: Minimum allowed SoC (%)
- `max_soc_percentage`: Maximum allowed SoC (%)
### Temperature Forecast
- Unit: °C
- Time Range: 48 hours (00:00 today to 23:00 tomorrow)
- Format: Array of hourly values
- Data Source: `GET /v1/prediction/list?key=weather_temp_air`
## Output Format
### Sample Response
```json
{
"ac_charge": [0.625, 0, ..., 0.75, 0],
"dc_charge": [1, 1, ..., 1, 1],
"discharge_allowed": [0, 0, 1, ..., 0, 0],
"eautocharge_hours_float": [0.625, 0, ..., 0.75, 0],
"result": {
"Last_Wh_pro_Stunde": [...],
"EAuto_SoC_pro_Stunde": [...],
"Einnahmen_Euro_pro_Stunde": [...],
"Gesamt_Verluste": 1514.96,
"Gesamtbilanz_Euro": 2.51,
"Gesamteinnahmen_Euro": 2.88,
"Gesamtkosten_Euro": 5.39,
"akku_soc_pro_stunde": [...]
}
}
```
### Output Parameters
#### Battery Control
- `ac_charge`: Grid charging schedule (0-1)
- `dc_charge`: DC charging schedule (0-1)
- `discharge_allowed`: Discharge permission (0 or 1)
0 (no charge)
1 (charge with full load)
`ac_charge` multiplied by the maximum charge power of the battery results in the planned charging power.
#### EV Charging
- `eautocharge_hours_float`: EV charging schedule (0-1)
#### Results
The `result` object contains detailed information about the optimization outcome.
The length of the array is between 25 and 48 and starts at the current hour and ends at 23:00 tomorrow.
- `Last_Wh_pro_Stunde`: Array of hourly load values in Wh
- Shows the total energy consumption per hour
- Includes household load, battery charging/discharging, and EV charging
- `EAuto_SoC_pro_Stunde`: Array of hourly EV state of charge values (%)
- Shows the projected EV battery level throughout the optimization period
- `Einnahmen_Euro_pro_Stunde`: Array of hourly revenue values in Euro
- `Gesamt_Verluste`: Total energy losses in Wh
- `Gesamtbilanz_Euro`: Overall financial balance in Euro
- `Gesamteinnahmen_Euro`: Total revenue in Euro
- `Gesamtkosten_Euro`: Total costs in Euro
- `akku_soc_pro_stunde`: Array of hourly battery state of charge values (%)
## Timeframe overview
```{figure} ../_static/optimization_timeframes.png
:alt: Timeframe Overview
Timeframe Overview
```

View File

@@ -5,10 +5,10 @@
Predictions, along with simulations and measurements, form the foundation upon which energy Predictions, along with simulations and measurements, form the foundation upon which energy
optimization is executed. In EOS, a standard set of predictions is managed, including: optimization is executed. In EOS, a standard set of predictions is managed, including:
- **Household Load Prediction** - Household Load Prediction
- **Electricity Price Prediction** - Electricity Price Prediction
- **PV Power Prediction** - PV Power Prediction
- **Weather Prediction** - Weather Prediction
## Storing Predictions ## Storing Predictions
@@ -60,13 +60,15 @@ A dictionary with the following structure:
#### 2. DateTimeDataFrame #### 2. DateTimeDataFrame
A JSON string created from a [pandas](https://pandas.pydata.org/docs/index.html) dataframe with a A JSON string created from a [pandas](https://pandas.pydata.org/docs/index.html) dataframe with a
`DatetimeIndex`. Use [pandas.DataFrame.to_json(orient="index")](https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.to_json.html#pandas.DataFrame.to_json). `DatetimeIndex`. Use
[pandas.DataFrame.to_json(orient="index")](https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.to_json.html#pandas.DataFrame.to_json).
The column name of the data must be the same as the names of the `prediction key`s. The column name of the data must be the same as the names of the `prediction key`s.
#### 3. DateTimeSeries #### 3. DateTimeSeries
A JSON string created from a [pandas](https://pandas.pydata.org/docs/index.html) series with a A JSON string created from a [pandas](https://pandas.pydata.org/docs/index.html) series with a
`DatetimeIndex`. Use [pandas.Series.to_json(orient="index")](https://pandas.pydata.org/docs/reference/api/pandas.Series.to_json.html#pandas.Series.to_json). `DatetimeIndex`. Use
[pandas.Series.to_json(orient="index")](https://pandas.pydata.org/docs/reference/api/pandas.Series.to_json.html#pandas.Series.to_json).
## Adjusted Predictions ## Adjusted Predictions
@@ -215,13 +217,21 @@ Configuration options:
- `PVForecastImport`: Imports from a file or JSON string. - `PVForecastImport`: Imports from a file or JSON string.
- `planes[].surface_tilt`: Tilt angle from horizontal plane. Ignored for two-axis tracking. - `planes[].surface_tilt`: Tilt angle from horizontal plane. Ignored for two-axis tracking.
- `planes[].surface_azimuth`: Orientation (azimuth angle) of the (fixed) plane. Clockwise from north (north=0, east=90, south=180, west=270). - `planes[].surface_azimuth`: Orientation (azimuth angle) of the (fixed) plane.
Clockwise from north (north=0, east=90, south=180, west=270).
- `planes[].userhorizon`: Elevation of horizon in degrees, at equally spaced azimuth clockwise from north. - `planes[].userhorizon`: Elevation of horizon in degrees, at equally spaced azimuth clockwise from north.
- `planes[].peakpower`: Nominal power of PV system in kW. - `planes[].peakpower`: Nominal power of PV system in kW.
- `planes[].pvtechchoice`: PV technology. One of 'crystSi', 'CIS', 'CdTe', 'Unknown'. - `planes[].pvtechchoice`: PV technology. One of 'crystSi', 'CIS', 'CdTe', 'Unknown'.
- `planes[].mountingplace`: Type of mounting for PV system. Options are 'free' for free-standing and 'building' for building-integrated. - `planes[].mountingplace`: Type of mounting for PV system.
Options are 'free' for free-standing and 'building' for building-integrated.
- `planes[].loss`: Sum of PV system losses in percent - `planes[].loss`: Sum of PV system losses in percent
- `planes[].trackingtype`: Type of suntracking. 0=fixed, 1=single horizontal axis aligned north-south, 2=two-axis tracking, 3=vertical axis tracking, 4=single horizontal axis aligned east-west, 5=single inclined axis aligned north-south. - `planes[].trackingtype`: Type of suntracking.
0=fixed,
1=single horizontal axis aligned north-south,
2=two-axis tracking,
3=vertical axis tracking,
4=single horizontal axis aligned east-west,
5=single inclined axis aligned north-south.
- `planes[].optimal_surface_tilt`: Calculate the optimum tilt angle. Ignored for two-axis tracking. - `planes[].optimal_surface_tilt`: Calculate the optimum tilt angle. Ignored for two-axis tracking.
- `planes[].optimalangles`: Calculate the optimum tilt and azimuth angles. Ignored for two-axis tracking. - `planes[].optimalangles`: Calculate the optimum tilt and azimuth angles. Ignored for two-axis tracking.
- `planes[].albedo`: Proportion of the light hitting the ground that it reflects back. - `planes[].albedo`: Proportion of the light hitting the ground that it reflects back.
@@ -235,37 +245,71 @@ Configuration options:
------ ------
Some of the planes configuration options directly follow the [PVGIS](https://joint-research-centre.ec.europa.eu/photovoltaic-geographical-information-system-pvgis/getting-started-pvgis/pvgis-user-manual_en) nomenclature. Detailed definitions taken from
[PVGIS](https://joint-research-centre.ec.europa.eu/photovoltaic-geographical-information-system-pvgis/getting-started-pvgis/pvgis-user-manual_en).
Detailed definitions taken from **PVGIS**:
- `pvtechchoice` - `pvtechchoice`
The performance of PV modules depends on the temperature and on the solar irradiance, but the exact dependence varies between different types of PV modules. At the moment we can estimate the losses due to temperature and irradiance effects for the following types of modules: crystalline silicon cells; thin film modules made from CIS or CIGS and thin film modules made from Cadmium Telluride (CdTe). The performance of PV modules depends on the temperature and on the solar irradiance, but the exact
dependence varies between different types of PV modules. At the moment we can estimate the losses
due to temperature and irradiance effects for the following types of modules: crystalline silicon
cells; thin film modules made from CIS or CIGS and thin film modules made from Cadmium Telluride
(CdTe).
For other technologies (especially various amorphous technologies), this correction cannot be calculated here. If you choose one of the first three options here the calculation of performance will take into account the temperature dependence of the performance of the chosen technology. If you choose the other option (other/unknown), the calculation will assume a loss of 8% of power due to temperature effects (a generic value which has found to be reasonable for temperate climates). For other technologies (especially various amorphous technologies), this correction cannot be
calculated here. If you choose one of the first three options here the calculation of performance
will take into account the temperature dependence of the performance of the chosen technology. If
you choose the other option (other/unknown), the calculation will assume a loss of 8% of power due
to temperature effects (a generic value which has found to be reasonable for temperate climates).
PV power output also depends on the spectrum of the solar radiation. PVGIS can calculate how the variations of the spectrum of sunlight affects the overall energy production from a PV system. At the moment this calculation can be done for crystalline silicon and CdTe modules. Note that this calculation is not yet available when using the NSRDB solar radiation database. PV power output also depends on the spectrum of the solar radiation. PVGIS can calculate how the
variations of the spectrum of sunlight affects the overall energy production from a PV system. At
the moment this calculation can be done for crystalline silicon and CdTe modules. Note that this
calculation is not yet available when using the NSRDB solar radiation database.
- `peakpower` - `peakpower`
This is the power that the manufacturer declares that the PV array can produce under standard test conditions (STC), which are a constant 1000W of solar irradiation per square meter in the plane of the array, at an array temperature of 25°C. The peak power should be entered in kilowatt-peak (kWp). If you do not know the declared peak power of your modules but instead know the area of the modules and the declared conversion efficiency (in percent), you can calculate the peak power as power = area * efficiency / 100. This is the power that the manufacturer declares that the PV array can produce under standard test
conditions (STC), which are a constant 1000W of solar irradiation per square meter in the plane of
the array, at an array temperature of 25°C. The peak power should be entered in kilowatt-peak (kWp).
If you do not know the declared peak power of your modules but instead know the area of the modules
and the declared conversion efficiency (in percent), you can calculate the peak power as
power = area * efficiency / 100.
Bifacial modules: PVGIS doesn't make specific calculations for bifacial modules at present. Users who wish to explore the possible benefits of this technology can input the power value for Bifacial Nameplate Irradiance. This can also be can also be estimated from the front side peak power P_STC value and the bifaciality factor, φ (if reported in the module data sheet) as: P_BNPI = P_STC * (1 + φ * 0.135). NB this bifacial approach is not appropriate for BAPV or BIPV installations or for modules mounting on a N-S axis i.e. facing E-W. Bifacial modules: PVGIS doesn't make specific calculations for bifacial modules at present. Users
who wish to explore the possible benefits of this technology can input the power value for Bifacial
Nameplate Irradiance. This can also be can also be estimated from the front side peak power P_STC
value and the bifaciality factor, φ (if reported in the module data sheet) as:
P_BNPI = P_STC \* (1 + φ \* 0.135). NB this bifacial approach is not appropriate for BAPV or BIPV
installations or for modules mounting on a N-S axis i.e. facing E-W.
- `loss` - `loss`
The estimated system losses are all the losses in the system, which cause the power actually delivered to the electricity grid to be lower than the power produced by the PV modules. There are several causes for this loss, such as losses in cables, power inverters, dirt (sometimes snow) on the modules and so on. Over the years the modules also tend to lose a bit of their power, so the average yearly output over the lifetime of the system will be a few percent lower than the output in the first years. The estimated system losses are all the losses in the system, which cause the power actually
delivered to the electricity grid to be lower than the power produced by the PV modules. There are
several causes for this loss, such as losses in cables, power inverters, dirt (sometimes snow) on
the modules and so on. Over the years the modules also tend to lose a bit of their power, so the
average yearly output over the lifetime of the system will be a few percent lower than the output
in the first years.
We have given a default value of 14% for the overall losses. If you have a good idea that your value will be different (maybe due to a really high-efficiency inverter) you may reduce this value a little. We have given a default value of 14% for the overall losses. If you have a good idea that your value
will be different (maybe due to a really high-efficiency inverter) you may reduce this value a little.
- `mountingplace` - `mountingplace`
For fixed (non-tracking) systems, the way the modules are mounted will have an influence on the temperature of the module, which in turn affects the efficiency. Experiments have shown that if the movement of air behind the modules is restricted, the modules can get considerably hotter (up to 15°C at 1000W/m2 of sunlight). For fixed (non-tracking) systems, the way the modules are mounted will have an influence on the
temperature of the module, which in turn affects the efficiency. Experiments have shown that if the
movement of air behind the modules is restricted, the modules can get considerably hotter
(up to 15°C at 1000W/m2 of sunlight).
In PVGIS there are two possibilities: free-standing, meaning that the modules are mounted on a rack with air flowing freely behind the modules; and building- integrated, which means that the modules are completely built into the structure of the wall or roof of a building, with no air movement behind the modules. In PVGIS there are two possibilities: free-standing, meaning that the modules are mounted on a rack
with air flowing freely behind the modules; and building- integrated, which means that the modules
are completely built into the structure of the wall or roof of a building, with no air movement
behind the modules.
Some types of mounting are in between these two extremes, for instance if the modules are mounted on a roof with curved roof tiles, allowing air to move behind the modules. In such cases, the performance will be somewhere between the results of the two calculations that are possible here. Some types of mounting are in between these two extremes, for instance if the modules are mounted on
a roof with curved roof tiles, allowing air to move behind the modules. In such cases, the
performance will be somewhere between the results of the two calculations that are possible here.
- `userhorizon` - `userhorizon`
@@ -279,7 +323,8 @@ degrees west of north.
------ ------
Most of the planes configuration options are in line with the [PVLib](https://pvlib-python.readthedocs.io/en/stable/_modules/pvlib/iotools/pvgis.html) definition for PVGIS data. Most of the configuration options are in line with the
[PVLib](https://pvlib-python.readthedocs.io/en/stable/_modules/pvlib/iotools/pvgis.html) definition for PVGIS data.
Detailed definitions from **PVLib** for PVGIS data. Detailed definitions from **PVLib** for PVGIS data.
@@ -307,7 +352,8 @@ The following prediction configuration options of the PV system must be set:
For each plane of the PV system the following configuration options must be set: For each plane of the PV system the following configuration options must be set:
- `pvforecast.planes[].surface_tilt`: Tilt angle from horizontal plane. Ignored for two-axis tracking. - `pvforecast.planes[].surface_tilt`: Tilt angle from horizontal plane. Ignored for two-axis tracking.
- `pvforecast.planes[].surface_azimuth`: Orientation (azimuth angle) of the (fixed) plane. Clockwise from north (north=0, east=90, south=180, west=270). - `pvforecast.planes[].surface_azimuth`: Orientation (azimuth angle) of the (fixed) plane.
Clockwise from north (north=0, east=90, south=180, west=270).
- `pvforecast.planes[].userhorizon`: Elevation of horizon in degrees, at equally spaced azimuth clockwise from north. - `pvforecast.planes[].userhorizon`: Elevation of horizon in degrees, at equally spaced azimuth clockwise from north.
- `pvforecast.planes[].inverter_paco`: AC power rating of the inverter. [W] - `pvforecast.planes[].inverter_paco`: AC power rating of the inverter. [W]
- `pvforecast.planes[].peakpower`: Nominal power of PV system in kW. - `pvforecast.planes[].peakpower`: Nominal power of PV system in kW.
@@ -407,8 +453,8 @@ Configuration options:
- `provider`: Load provider id of provider to be used. - `provider`: Load provider id of provider to be used.
- `BrightSky`: Retrieves from https://api.brightsky.dev. - `BrightSky`: Retrieves from [BrightSky](https://api.brightsky.dev).
- `ClearOutside`: Retrieves from https://clearoutside.com/forecast. - `ClearOutside`: Retrieves from [ClearOutside](https://clearoutside.com/forecast).
- `LoadImport`: Imports from a file or JSON string. - `LoadImport`: Imports from a file or JSON string.
- `provider_settings.import_file_path`: Path to the file to import weatherforecast data from. - `provider_settings.import_file_path`: Path to the file to import weatherforecast data from.

View File

@@ -75,37 +75,53 @@ This project uses the `EOS.config.json` file to manage configuration settings.
### Default Configuration ### Default Configuration
A default configuration file `default.config.json` is provided. This file contains all the necessary configuration keys with their default values. A default configuration file `default.config.json` is provided. This file contains all the necessary
configuration keys with their default values.
### Custom Configuration ### Custom Configuration
Users can specify a custom configuration directory by setting the environment variable `EOS_DIR`. Users can specify a custom configuration directory by setting the environment variable `EOS_DIR`.
- If the directory specified by `EOS_DIR` contains an existing `EOS.config.json` file, the application will use this configuration file. - If the directory specified by `EOS_DIR` contains an existing `EOS.config.json` file, the
- If the `EOS.config.json` file does not exist in the specified directory, the `default.config.json` file will be copied to the directory as `EOS.config.json`. application will use this configuration file.
- If the `EOS.config.json` file does not exist in the specified directory, the `default.config.json`
file will be copied to the directory as `EOS.config.json`.
### Configuration Updates ### Configuration Updates
If the configuration keys in the `EOS.config.json` file are missing or different from those in `default.config.json`, they will be automatically updated to match the default settings, ensuring that all required keys are present. If the configuration keys in the `EOS.config.json` file are missing or different from those in
`default.config.json`, they will be automatically updated to match the default settings, ensuring
that all required keys are present.
## Classes and Functionalities ## Classes and Functionalities
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:
- `Battery`: 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.
- `Load`: Models the load requirements of a household or business, enabling the prediction of future energy demand. - `Load`: Models the load requirements of a household or business, enabling the prediction of future
energy demand.
- `Heatpump`: Simulates a heat pump, including its energy consumption and efficiency under various operating conditions. - `Heatpump`: Simulates a heat pump, including its energy consumption and efficiency under various
operating conditions.
- `Strompreis`: Provides information on electricity prices, enabling optimization of energy consumption and generation based on tariff information. - `Strompreis`: Provides information on electricity prices, enabling optimization of energy
consumption and generation based on tariff information.
- `EMS`: The Energy Management System (EMS) coordinates the interaction between the various components, performs optimization, and simulates the operation of the entire energy system. - `EMS`: The Energy Management System (EMS) coordinates the interaction between the various
components, performs optimization, and simulates the operation of the entire energy system.
These classes work together to enable a detailed simulation and optimization of the energy system. For each class, specific parameters and settings can be adjusted to test different scenarios and strategies. These classes work together to enable a detailed simulation and optimization of the energy system.
For each class, specific parameters and settings can be adjusted to test different scenarios and
strategies.
### Customization and Extension ### Customization and Extension
Each class is designed to be easily customized and extended to integrate additional functions or improvements. For example, new methods can be added for more accurate modeling of PV system or battery behavior. Developers are invited to modify and extend the system according to their needs. Each class is designed to be easily customized and extended to integrate additional functions or
improvements. For example, new methods can be added for more accurate modeling of PV system or
battery behavior. Developers are invited to modify and extend the system according to their needs.

View File

@@ -24,7 +24,7 @@ akkudoktoreos/serverapi.md
akkudoktoreos/api.rst akkudoktoreos/api.rst
``` ```
# Indices and tables ## Indices and tables
- {ref}`genindex` - {ref}`genindex`
- {ref}`modindex` - {ref}`modindex`

20
docs/pymarkdown.json Normal file
View File

@@ -0,0 +1,20 @@
{
"plugins": {
"md007": {
"enabled": true,
"code_block_line_length" : 160
},
"md013": {
"enabled": true,
"line_length" : 120
},
"md041": {
"enabled": false
}
},
"extensions": {
"front-matter" : {
"enabled" : true
}
}
}

View File

@@ -1,12 +1,12 @@
% SPDX-License-Identifier: Apache-2.0 % SPDX-License-Identifier: Apache-2.0
# Welcome to the EOS documentation! # Welcome to the EOS documentation
This documentation is continuously written. It is edited via text files in the This documentation is continuously written. It is edited via text files in the
[Markdown/ Markedly Structured Text](https://myst-parser.readthedocs.io/en/latest/index.html) [Markdown/ Markedly Structured Text](https://myst-parser.readthedocs.io/en/latest/index.html)
markup language and then compiled into a static website/ offline document using the open source tool markup language and then compiled into a static website/ offline document using the open source tool
[Sphinx](https://www.sphinx-doc.org) and will someday land on [Sphinx](https://www.sphinx-doc.org) and is available on
[Read the Docs](https://akkudoktoreos.readthedocs.io/en/latest/index.html). [Read the Docs](https://akkudoktor-eos.readthedocs.io/en/latest/).
You can contribute to EOS's documentation by opening You can contribute to EOS's documentation by opening
[GitHub issues](https://github.com/Akkudoktor-EOS/EOS/issues) [GitHub issues](https://github.com/Akkudoktor-EOS/EOS/issues)

View File

@@ -7,7 +7,7 @@ authors = [
description = "This project provides a comprehensive solution for simulating and optimizing an energy system based on renewable energy sources. With a focus on photovoltaic (PV) systems, battery storage (batteries), load management (consumer requirements), heat pumps, electric vehicles, and consideration of electricity price data, this system enables forecasting and optimization of energy flow and costs over a specified period." description = "This project provides a comprehensive solution for simulating and optimizing an energy system based on renewable energy sources. With a focus on photovoltaic (PV) systems, battery storage (batteries), load management (consumer requirements), heat pumps, electric vehicles, and consideration of electricity price data, this system enables forecasting and optimization of energy flow and costs over a specified period."
readme = "README.md" readme = "README.md"
license = {file = "LICENSE"} license = {file = "LICENSE"}
requires-python = ">=3.10" requires-python = ">=3.11"
classifiers = [ classifiers = [
"Development Status :: 3 - Alpha", "Development Status :: 3 - Alpha",
"Programming Language :: Python :: 3", "Programming Language :: Python :: 3",

View File

@@ -1,13 +1,13 @@
-r requirements.txt -r requirements.txt
gitpython==3.1.44 gitpython==3.1.44
myst-parser==4.0.0 myst-parser==4.0.1
sphinx==8.1.3 sphinx==8.2.3
sphinx_rtd_theme==3.0.2 sphinx_rtd_theme==3.0.2
sphinx-tabs==3.4.7 sphinx-tabs==3.4.7
pytest==8.3.4 pytest==8.3.5
pytest-cov==6.0.0 pytest-cov==6.0.0
pytest-xprocess==1.0.2 pytest-xprocess==1.0.2
pre-commit pre-commit
mypy==1.13.0 mypy==1.15.0
types-requests==2.32.0.20241016 types-requests==2.32.0.20250306
pandas-stubs==2.2.3.241126 pandas-stubs==2.2.3.250308

View File

@@ -1,9 +1,9 @@
cachebox==4.4.2 cachebox==4.4.2
numpy==2.2.2 numpy==2.2.4
numpydantic==1.6.7 numpydantic==1.6.8
matplotlib==3.10.0 matplotlib==3.10.1
fastapi[standard]==0.115.7 fastapi[standard]==0.115.11
python-fasthtml==0.12.0 python-fasthtml==0.12.4
MonsterUI==0.0.29 MonsterUI==0.0.29
markdown-it-py==3.0.0 markdown-it-py==3.0.0
mdit-py-plugins==0.4.2 mdit-py-plugins==0.4.2
@@ -15,9 +15,9 @@ deap==1.4.2
requests==2.32.3 requests==2.32.3
pandas==2.2.3 pandas==2.2.3
pendulum==3.0.0 pendulum==3.0.0
platformdirs==4.3.6 platformdirs==4.3.7
psutil==6.1.1 psutil==6.1.1
pvlib==0.11.2 pvlib==0.12.0
pydantic==2.10.6 pydantic==2.10.6
statsmodels==0.14.4 statsmodels==0.14.4
pydantic-settings==2.7.0 pydantic-settings==2.7.0

View File

@@ -811,7 +811,8 @@ class DataSequence(DataBase, MutableSequence):
dates, values = self.key_to_lists( dates, values = self.key_to_lists(
key=key, start_datetime=start_datetime, end_datetime=end_datetime, dropna=dropna key=key, start_datetime=start_datetime, end_datetime=end_datetime, dropna=dropna
) )
return pd.Series(data=values, index=pd.DatetimeIndex(dates), name=key) series = pd.Series(data=values, index=pd.DatetimeIndex(dates), name=key)
return series
def key_from_series(self, key: str, series: pd.Series) -> None: def key_from_series(self, key: str, series: pd.Series) -> None:
"""Update the DataSequence from a Pandas Series. """Update the DataSequence from a Pandas Series.

View File

@@ -7,7 +7,7 @@ format, enabling consistent access to forecasted and historical weather attribut
""" """
import json import json
from typing import Dict, List, Optional, Tuple from typing import Dict, List, Optional, Tuple, Union
import pandas as pd import pandas as pd
import pvlib import pvlib
@@ -16,14 +16,14 @@ import requests
from akkudoktoreos.core.cache import cache_in_file from akkudoktoreos.core.cache import cache_in_file
from akkudoktoreos.core.logging import get_logger from akkudoktoreos.core.logging import get_logger
from akkudoktoreos.prediction.weatherabc import WeatherDataRecord, WeatherProvider from akkudoktoreos.prediction.weatherabc import WeatherDataRecord, WeatherProvider
from akkudoktoreos.utils.datetimeutil import to_datetime from akkudoktoreos.utils.datetimeutil import to_datetime, to_duration
logger = get_logger(__name__) logger = get_logger(__name__)
WheaterDataBrightSkyMapping: List[Tuple[str, Optional[str], Optional[float]]] = [ WheaterDataBrightSkyMapping: List[Tuple[str, Optional[str], Optional[Union[str, float]]]] = [
# brightsky_key, description, corr_factor # brightsky_key, description, corr_factor
("timestamp", "DateTime", None), ("timestamp", "DateTime", "to datetime in timezone"),
("precipitation", "Precipitation Amount (mm)", 1), ("precipitation", "Precipitation Amount (mm)", 1),
("pressure_msl", "Pressure (mb)", 1), ("pressure_msl", "Pressure (mb)", 1),
("sunshine", None, None), ("sunshine", None, None),
@@ -96,8 +96,8 @@ class WeatherBrightSky(WeatherProvider):
ValueError: If the API response does not include expected `weather` data. ValueError: If the API response does not include expected `weather` data.
""" """
source = "https://api.brightsky.dev" source = "https://api.brightsky.dev"
date = to_datetime(self.start_datetime, as_string="YYYY-MM-DD") date = to_datetime(self.start_datetime, as_string=True)
last_date = to_datetime(self.end_datetime, as_string="YYYY-MM-DD") last_date = to_datetime(self.end_datetime, as_string=True)
response = requests.get( response = requests.get(
f"{source}/weather?lat={self.config.general.latitude}&lon={self.config.general.longitude}&date={date}&last_date={last_date}&tz={self.config.general.timezone}" f"{source}/weather?lat={self.config.general.latitude}&lon={self.config.general.longitude}&date={date}&last_date={last_date}&tz={self.config.general.timezone}"
) )
@@ -133,7 +133,8 @@ class WeatherBrightSky(WeatherProvider):
error_msg = f"No WeatherDataRecord key for '{description}'" error_msg = f"No WeatherDataRecord key for '{description}'"
logger.error(error_msg) logger.error(error_msg)
raise ValueError(error_msg) raise ValueError(error_msg)
return self.key_to_series(key) series = self.key_to_series(key)
return series
def _description_from_series(self, description: str, data: pd.Series) -> None: def _description_from_series(self, description: str, data: pd.Series) -> None:
"""Update a weather data with a pandas Series based on its description. """Update a weather data with a pandas Series based on its description.
@@ -170,7 +171,7 @@ class WeatherBrightSky(WeatherProvider):
brightsky_data = self._request_forecast(force_update=force_update) # type: ignore brightsky_data = self._request_forecast(force_update=force_update) # type: ignore
# Get key mapping from description # Get key mapping from description
brightsky_key_mapping: Dict[str, Tuple[Optional[str], Optional[float]]] = {} brightsky_key_mapping: Dict[str, Tuple[Optional[str], Optional[Union[str, float]]]] = {}
for brightsky_key, description, corr_factor in WheaterDataBrightSkyMapping: for brightsky_key, description, corr_factor in WheaterDataBrightSkyMapping:
if description is None: if description is None:
brightsky_key_mapping[brightsky_key] = (None, None) brightsky_key_mapping[brightsky_key] = (None, None)
@@ -192,6 +193,9 @@ class WeatherBrightSky(WeatherProvider):
value = brightsky_record[brightsky_key] value = brightsky_record[brightsky_key]
corr_factor = item[1] corr_factor = item[1]
if value and corr_factor: if value and corr_factor:
if corr_factor == "to datetime in timezone":
value = to_datetime(value, in_timezone=self.config.general.timezone)
else:
value = value * corr_factor value = value * corr_factor
setattr(weather_record, key, value) setattr(weather_record, key, value)
self.insert_by_datetime(weather_record) self.insert_by_datetime(weather_record)
@@ -216,14 +220,30 @@ class WeatherBrightSky(WeatherProvider):
self._description_from_series(description, dhi) self._description_from_series(description, dhi)
# Add Preciptable Water (PWAT) with a PVLib method. # Add Preciptable Water (PWAT) with a PVLib method.
description = "Temperature (°C)" key = WeatherDataRecord.key_from_description("Temperature (°C)")
temperature = self._description_to_series(description) assert key
temperature = self.key_to_array(
description = "Relative Humidity (%)" key=key,
humidity = self._description_to_series(description) start_datetime=self.start_datetime,
end_datetime=self.end_datetime,
interval=to_duration("1 hour"),
)
key = WeatherDataRecord.key_from_description("Relative Humidity (%)")
assert key
humidity = self.key_to_array(
key=key,
start_datetime=self.start_datetime,
end_datetime=self.end_datetime,
interval=to_duration("1 hour"),
)
data = pvlib.atmosphere.gueymard94_pw(temperature, humidity)
pwat = pd.Series( pwat = pd.Series(
data=pvlib.atmosphere.gueymard94_pw(temperature, humidity), index=temperature.index data=data,
index=pd.DatetimeIndex(
pd.date_range(
start=self.start_datetime, end=self.end_datetime, freq="1h", inclusive="left"
)
),
) )
description = "Preciptable Water (cm)" description = "Preciptable Water (cm)"
self._description_from_series(description, pwat) self._description_from_series(description, pwat)

View File

@@ -4,7 +4,7 @@ from typing import Union
import pandas as pd import pandas as pd
import requests import requests
from bokeh.models import ColumnDataSource, Range1d from bokeh.models import ColumnDataSource, LinearAxis, Range1d
from bokeh.plotting import figure from bokeh.plotting import figure
from monsterui.franken import FT, Grid, P from monsterui.franken import FT, Grid, P
@@ -135,6 +135,52 @@ def DemoWeatherIrradiance(predictions: pd.DataFrame, config: dict) -> FT:
return Bokeh(plot) return Bokeh(plot)
def DemoLoad(predictions: pd.DataFrame, config: dict) -> FT:
source = ColumnDataSource(predictions)
provider = config["load"]["provider"]
if provider == "LoadAkkudoktor":
year_energy = config["load"]["provider_settings"]["loadakkudoktor_year_energy"]
provider = f"{provider}, {year_energy} kWh"
plot = figure(
x_axis_type="datetime",
title=f"Load Prediction ({provider})",
x_axis_label="Datetime",
y_axis_label="Load [W]",
sizing_mode="stretch_width",
height=400,
)
plot.extra_y_ranges["stddev"] = Range1d(0, 1000)
y2_axis = LinearAxis(y_range_name="stddev", axis_label="Load Standard Deviation [W]")
y2_axis.axis_label_text_color = "green"
plot.add_layout(y2_axis, "left")
plot.line(
"date_time",
"load_mean",
source=source,
legend_label="Load mean value",
color="red",
)
plot.line(
"date_time",
"load_mean_adjusted",
source=source,
legend_label="Load adjusted by measurement",
color="blue",
)
plot.line(
"date_time",
"load_std",
source=source,
legend_label="Load standard deviation",
color="green",
y_range_name="stddev",
)
return Bokeh(plot)
def Demo(eos_host: str, eos_port: Union[str, int]) -> str: def Demo(eos_host: str, eos_port: Union[str, int]) -> str:
server = f"http://{eos_host}:{eos_port}" server = f"http://{eos_host}:{eos_port}"
@@ -188,6 +234,9 @@ def Demo(eos_host: str, eos_port: Union[str, int]) -> str:
"weather_ghi", "weather_ghi",
"weather_dni", "weather_dni",
"weather_dhi", "weather_dhi",
"load_mean",
"load_std",
"load_mean_adjusted",
], ],
} }
result = requests.get(f"{server}/v1/prediction/dataframe", params=params) result = requests.get(f"{server}/v1/prediction/dataframe", params=params)
@@ -213,5 +262,6 @@ def Demo(eos_host: str, eos_port: Union[str, int]) -> str:
DemoElectricityPriceForecast(predictions, democonfig), DemoElectricityPriceForecast(predictions, democonfig),
DemoWeatherTempAir(predictions, democonfig), DemoWeatherTempAir(predictions, democonfig),
DemoWeatherIrradiance(predictions, democonfig), DemoWeatherIrradiance(predictions, democonfig),
DemoLoad(predictions, democonfig),
cols_max=2, cols_max=2,
) )

View File

@@ -7,6 +7,7 @@ import os
import signal import signal
import subprocess import subprocess
import sys import sys
import traceback
from contextlib import asynccontextmanager from contextlib import asynccontextmanager
from pathlib import Path from pathlib import Path
from typing import Annotated, Any, AsyncGenerator, Dict, List, Optional, Union from typing import Annotated, Any, AsyncGenerator, Dict, List, Optional, Union
@@ -844,7 +845,11 @@ def fastapi_prediction_update(
try: try:
prediction_eos.update_data(force_update=force_update, force_enable=force_enable) prediction_eos.update_data(force_update=force_update, force_enable=force_enable)
except Exception as e: except Exception as e:
raise HTTPException(status_code=400, detail=f"Error on prediction update: {e}") trace = "".join(traceback.TracebackException.from_exception(e).format())
raise HTTPException(
status_code=400,
detail=f"Error on prediction update: {e}{trace}",
)
return Response() return Response()
@@ -868,7 +873,9 @@ def fastapi_prediction_update_provider(
try: try:
provider.update_data(force_update=force_update, force_enable=force_enable) provider.update_data(force_update=force_update, force_enable=force_enable)
except Exception as e: except Exception as e:
raise HTTPException(status_code=400, detail=f"Error on update of provider: {e}") raise HTTPException(
status_code=400, detail=f"Error on update of provider '{provider_id}': {e}"
)
return Response() return Response()

View File

@@ -162,10 +162,7 @@ def test_update_data(mock_get, provider, sample_brightsky_1_json, cache_store):
# Assert: Verify the result is as expected # Assert: Verify the result is as expected
mock_get.assert_called_once() mock_get.assert_called_once()
assert len(provider) == 338 assert len(provider) == 50
# with open(FILE_TESTDATA_WEATHERBRIGHTSKY_2_JSON, "w") as f_out:
# f_out.write(provider.to_json())
# ------------------------------------------------ # ------------------------------------------------
@@ -188,3 +185,8 @@ def test_brightsky_development_forecast_data(provider, config_eos, is_system_tes
with FILE_TESTDATA_WEATHERBRIGHTSKY_1_JSON.open("w", encoding="utf-8", newline="\n") as f_out: with FILE_TESTDATA_WEATHERBRIGHTSKY_1_JSON.open("w", encoding="utf-8", newline="\n") as f_out:
json.dump(brightsky_data, f_out, indent=4) json.dump(brightsky_data, f_out, indent=4)
provider.update_data(force_enable=True, force_update=True)
with FILE_TESTDATA_WEATHERBRIGHTSKY_2_JSON.open("w", encoding="utf-8", newline="\n") as f_out:
f_out.write(provider.model_dump_json(indent=4))

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff