mirror of
https://github.com/Akkudoktor-EOS/EOS.git
synced 2025-10-11 11:56:17 +00:00
Compare commits
36 Commits
NormannK-p
...
NormannK-p
Author | SHA1 | Date | |
---|---|---|---|
|
447f7d05be | ||
|
c72051a08e | ||
|
60bd320fbc | ||
|
600e332aae | ||
|
6a51de04da | ||
|
64c8415714 | ||
|
52f8b015b1 | ||
|
a4adb07ebf | ||
|
b69bbe897f | ||
|
0a7420c42b | ||
|
b22b5ee651 | ||
|
9b4ec74823 | ||
|
1f30d4e403 | ||
|
b563bbbd98 | ||
|
8422a5c9d8 | ||
|
bec5c2cbda | ||
|
d2136f1447 | ||
|
20621aa626 | ||
|
76b5ec3638 | ||
|
d912561bfb | ||
|
5907c94a2e | ||
|
7b9b58f1e0 | ||
|
c87bf2e4fc | ||
|
7773c4c2c9 | ||
|
b380624c9f | ||
|
caed880672 | ||
|
6cc9a5fd44 | ||
|
80a4079bbf | ||
|
cc687b140f | ||
|
29cf3a3174 | ||
|
837595de56 | ||
|
1a2da7636b | ||
|
774cfd8b65 | ||
|
9170b5f5cd | ||
|
e42dd5d4b2 | ||
|
58f077b4ae |
1
.env
1
.env
@@ -1,4 +1,5 @@
|
||||
EOS_VERSION=main
|
||||
EOS_PORT=8503
|
||||
EOSDASH_PORT=8504
|
||||
|
||||
PYTHON_VERSION=3.12.6
|
||||
|
9
.github/dependabot.yml
vendored
9
.github/dependabot.yml
vendored
@@ -5,7 +5,16 @@
|
||||
|
||||
version: 2
|
||||
updates:
|
||||
# Update dependencies on the main branch
|
||||
- package-ecosystem: "pip" # See documentation for possible values
|
||||
directory: "/" # Location of package manifests
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
target-branch: "main" # Target the main branch
|
||||
|
||||
# Update dependencies on the feature/config-nested branch
|
||||
- package-ecosystem: "pip"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
target-branch: "feature/config-nested" # Target the specific feature branch
|
||||
|
@@ -33,3 +33,12 @@ repos:
|
||||
- "pandas-stubs==2.2.3.241009"
|
||||
- "numpy==2.1.3"
|
||||
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
|
||||
|
@@ -6,7 +6,7 @@ The `EOS` project is in early development, therefore we encourage contribution i
|
||||
|
||||
## Documentation
|
||||
|
||||
Latest development documentation can be found at [Akkudoktor-EOS](https://akkudoktor-eos.readthedocs.io/en/main/).
|
||||
Latest development documentation can be found at [Akkudoktor-EOS](https://akkudoktor-eos.readthedocs.io/en/latest/).
|
||||
|
||||
## Bug Reports
|
||||
|
||||
@@ -33,6 +33,7 @@ See also [README.md](README.md).
|
||||
python -m venv .venv
|
||||
source .venv/bin/activate
|
||||
pip install -r requirements-dev.txt
|
||||
pip install -e .
|
||||
```
|
||||
|
||||
Install make to get access to helpful shortcuts (documentation generation, manual formatting, etc.).
|
||||
|
@@ -11,6 +11,9 @@ ENV EOS_CACHE_DIR="${EOS_DIR}/cache"
|
||||
ENV EOS_OUTPUT_DIR="${EOS_DIR}/output"
|
||||
ENV EOS_CONFIG_DIR="${EOS_DIR}/config"
|
||||
|
||||
# Overwrite when starting the container in a production environment
|
||||
ENV EOS_SERVER__EOSDASH_SESSKEY=s3cr3t
|
||||
|
||||
WORKDIR ${EOS_DIR}
|
||||
|
||||
RUN adduser --system --group --no-create-home eos \
|
||||
@@ -39,6 +42,7 @@ ENTRYPOINT []
|
||||
EXPOSE 8503
|
||||
EXPOSE 8504
|
||||
|
||||
ENV server_eosdash_host=0.0.0.0
|
||||
CMD ["python", "src/akkudoktoreos/server/eos.py", "--host", "0.0.0.0"]
|
||||
|
||||
VOLUME ["${MPLCONFIGDIR}", "${EOS_CACHE_DIR}", "${EOS_OUTPUT_DIR}", "${EOS_CONFIG_DIR}"]
|
||||
|
4
Makefile
4
Makefile
@@ -17,8 +17,8 @@ help:
|
||||
@echo " docker-build - Rebuild docker image"
|
||||
@echo " docs - Generate HTML documentation (in build/docs/html/)."
|
||||
@echo " read-docs - Read HTML documentation in your browser."
|
||||
@echo " gen-docs - Generate openapi.json and docs/_generated/*.""
|
||||
@echo " clean-docs - Remove generated documentation.""
|
||||
@echo " gen-docs - Generate openapi.json and docs/_generated/*."
|
||||
@echo " clean-docs - Remove generated documentation."
|
||||
@echo " run - Run EOS production server in the virtual environment."
|
||||
@echo " run-dev - Run EOS development server in the virtual environment (automatically reloads)."
|
||||
@echo " dist - Create distribution (in dist/)."
|
||||
|
18
README.md
18
README.md
@@ -8,9 +8,19 @@ Documentation can be found at [Akkudoktor-EOS](https://akkudoktor-eos.readthedoc
|
||||
|
||||
See [CONTRIBUTING.md](CONTRIBUTING.md).
|
||||
|
||||
## System requirements
|
||||
|
||||
- Python >= 3.11, < 3.13
|
||||
- Architecture: amd64, aarch64 (armv8)
|
||||
- OS: Linux, Windows, macOS
|
||||
|
||||
Note: For Python 3.13 some dependencies (e.g. [Pendulum](https://github.com/python-pendulum/Pendulum)) are not yet available on https://pypi.org and have to be manually compiled (a recent [Rust](https://www.rust-lang.org/tools/install) installation is required).
|
||||
|
||||
Other architectures (e.g. armv6, armv7) are unsupported for now, because a multitude of dependencies are not available on https://piwheels.org and have to be built manually (a recent Rust installation and [GCC](https://gcc.gnu.org/) are required, Python 3.11 is recommended).
|
||||
|
||||
## 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).
|
||||
Docker images (amd64/aarch64) 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`.
|
||||
|
||||
@@ -23,6 +33,7 @@ Linux:
|
||||
```bash
|
||||
python -m venv .venv
|
||||
.venv/bin/pip install -r requirements.txt
|
||||
.venv/bin/pip install -e .
|
||||
```
|
||||
|
||||
Windows:
|
||||
@@ -30,9 +41,10 @@ Windows:
|
||||
```cmd
|
||||
python -m venv .venv
|
||||
.venv\Scripts\pip install -r requirements.txt
|
||||
.venv\Scripts\pip install -e .
|
||||
```
|
||||
|
||||
Finally, start the EOS server:
|
||||
Finally, start the EOS server to access it at `http://localhost:8503` (API docs at `http://localhost:8503/docs`):
|
||||
|
||||
Linux:
|
||||
|
||||
@@ -48,6 +60,8 @@ Windows:
|
||||
|
||||
### Docker
|
||||
|
||||
Start EOS with following command to access it at `http://localhost:8503` (API docs at `http://localhost:8503/docs`):
|
||||
|
||||
```bash
|
||||
docker compose up
|
||||
```
|
||||
|
@@ -17,6 +17,8 @@ services:
|
||||
- longitude=13.4
|
||||
- elecprice_provider=ElecPriceAkkudoktor
|
||||
- elecprice_charges_kwh=0.21
|
||||
- server_fasthtml_host=none
|
||||
- EOS_SERVER__EOSDASH_SESSKEY=s3cr3t
|
||||
ports:
|
||||
- "${EOS_PORT}:${EOS_PORT}"
|
||||
# Configure what ports to expose on host
|
||||
- "${EOS_PORT}:8503"
|
||||
- "${EOSDASH_PORT}:8504"
|
||||
|
BIN
docs/_static/introduction/integration.png
vendored
Executable file
BIN
docs/_static/introduction/integration.png
vendored
Executable file
Binary file not shown.
After Width: | Height: | Size: 58 KiB |
BIN
docs/_static/introduction/introduction.png
vendored
Executable file
BIN
docs/_static/introduction/introduction.png
vendored
Executable file
Binary file not shown.
After Width: | Height: | Size: 22 KiB |
BIN
docs/_static/introduction/overview.png
vendored
Executable file
BIN
docs/_static/introduction/overview.png
vendored
Executable file
Binary file not shown.
After Width: | Height: | Size: 60 KiB |
1
docs/_static/optimization_timeframes-excalidraw.json
vendored
Normal file
1
docs/_static/optimization_timeframes-excalidraw.json
vendored
Normal file
File diff suppressed because one or more lines are too long
BIN
docs/_static/optimization_timeframes.png
vendored
Normal file
BIN
docs/_static/optimization_timeframes.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 664 KiB |
@@ -1,9 +0,0 @@
|
||||
% SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
# About Akkudoktor EOS
|
||||
|
||||
The Energy System Simulation and Optimization System (EOS) 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.
|
@@ -20,17 +20,22 @@ EOS Architecture
|
||||
|
||||
### 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 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
|
||||
|
||||
### 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
|
||||
|
||||
@@ -38,7 +43,8 @@ Predictions provide predicted future data to be used by the optimization.
|
||||
|
||||
### 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
|
||||
|
||||
|
@@ -31,10 +31,10 @@ Use endpoint `POST /v1/config/update` to update the configuration from the `EOS
|
||||
|
||||
The configuration sources and their priorities are as follows:
|
||||
|
||||
1. **Settings**: Provided during runtime by the REST interface
|
||||
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
|
||||
4. **Default Values**
|
||||
1. `Settings`: Provided during runtime by the REST interface
|
||||
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
|
||||
4. `Default Values`
|
||||
|
||||
### Settings
|
||||
|
||||
|
@@ -1,4 +1,5 @@
|
||||
% SPDX-License-Identifier: Apache-2.0
|
||||
(integration-page)=
|
||||
|
||||
# Integration
|
||||
|
||||
@@ -17,18 +18,19 @@ APIs, and online services in creative and practical ways.
|
||||
|
||||
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
|
||||
`Node-RED`.
|
||||
- [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 `Node-RED`.
|
||||
|
||||
## Home Assistant
|
||||
|
||||
[Home Assistant](https://www.home-assistant.io/) is an open-source home automation platform that
|
||||
emphasizes local control and user privacy.
|
||||
|
||||
### Resources
|
||||
(duetting-solution)=
|
||||
|
||||
### Home Assistant Resources
|
||||
|
||||
- Duetting's [EOS Home Assistant Addon](https://github.com/Duetting/ha_eos_addon) — Additional
|
||||
details can be found in this
|
||||
[discussion thread](https://github.com/Akkudoktor-EOS/EOS/discussions/294).
|
||||
details can be found in this [discussion thread](https://github.com/Akkudoktor-EOS/EOS/discussions/294).
|
||||
|
180
docs/akkudoktoreos/introduction.md
Normal file
180
docs/akkudoktoreos/introduction.md
Normal file
@@ -0,0 +1,180 @@
|
||||
% SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
# Introduction
|
||||
|
||||
The Energy System Simulation and Optimization System (EOS) 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.
|
||||
|
||||
After successfully installing a PV system with or without battery storage, most owners
|
||||
first priority is often to charge the electric car with surplus energy in order to use
|
||||
the electricity generated by the PV system cost-effectively for electromobility.
|
||||
|
||||
After initial experiences, the desire to include battery storage and dynamic electricity
|
||||
prices in the solution soon arises. The market already offers various commercial and
|
||||
non-commercial solutions for this, such as the popular open source hardware and software
|
||||
solutions evcc or openWB.
|
||||
|
||||
Some solutions take into account the current values of the system such as PV power
|
||||
output, battery storage charge level or the current electricity price to decide whether
|
||||
to charge the electric car with PV surplus or from the grid (e.g. openWB), some use
|
||||
historical consumption values and PV forecast data for their calculations, but leave out
|
||||
the current electricity prices and charging the battery storage from the power grid
|
||||
(Predbat). Others are specialiced on working in combination with a specific smart home
|
||||
solution (e.g. emhass). Still others focus on certain consumers, such as the electric car,
|
||||
or are currently working on integrating the forecast values (evcc). And some are commercial
|
||||
devices that require an electrician to install them and expect a certain ecosystem
|
||||
(e.g. Sunny Home Manager).
|
||||
|
||||
The Akkudoktor EOS
|
||||
|
||||
- takes into account historical, current and forecast data such as consumption values, PV
|
||||
forecast data, electricity price forecast, battery storage and electric car charge levels
|
||||
- the simulation also takes into account the possibility of charging the battery storage
|
||||
from the grid at low electricity prices
|
||||
- is not limited to certain consumers, but includes electric cars, heat pumps or more
|
||||
powerful consumers such as tumble dryers
|
||||
- is independent of a specific smart home solution and can also be integrated into
|
||||
self-developed solutions if desired
|
||||
- is a free and independent open source software solution
|
||||
|
||||

|
||||
|
||||
The challenge is to charge (electric car) or start the consumers (washing machine, dryer)
|
||||
at the right time and to do so as cost-efficiently as possible. If PV yield forecast,
|
||||
battery storage and dynamic electricity price forecasts are included in the calculation,
|
||||
the possibilities increase, but unfortunately so does the complexity.
|
||||
|
||||
The Akkudoktor EOS addresses this challenge by simulating energy flows in the household
|
||||
based on target values, forecast data and current operating data over a 48-hour
|
||||
observation period, running through a large number of different scenarios and finally
|
||||
providing a cost-optimized plan for the current day controlling the relevant consumers.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Technical requirements
|
||||
- Input data
|
||||
|
||||
### Technical requirements
|
||||
|
||||
- reasonably fast computer on which EOS is installed
|
||||
- controllable energy system consisting of photovoltaic system, solar battery storage,
|
||||
energy intensive consumers that must provide the appropriate interfaces
|
||||
- integration solution for integrating the energy system and EOS
|
||||
|
||||
### Input Data
|
||||
|
||||

|
||||
|
||||
The EOS requires various types of data for the simulation:
|
||||
|
||||
Forecast data
|
||||
|
||||
- PV yield forecast
|
||||
- Expected household consumption
|
||||
- Electricity price forecast
|
||||
- Forecast temperature trend (if heatpump is used)
|
||||
|
||||
Basic data and current operating data
|
||||
|
||||
- Current charge level of the battery storage
|
||||
- Value of electricity in the battery storage
|
||||
- Current charge level of the electric car
|
||||
- Energy consumption and running time of dishwasher, washing machine and tumble dryer
|
||||
|
||||
Target values
|
||||
|
||||
- Charge level the electric car should reach in the next few hours
|
||||
- Consumers to run in the next few hours
|
||||
|
||||
There are various service providers available for PV forecasting that calculate forecast
|
||||
data for a PV system based on the various influencing factors, such as system size,
|
||||
orientation, location, time of year and weather conditions. EOS also offers a
|
||||
[PV forecasting service](#prediction-page) which can be used. This service uses
|
||||
public data in the background.
|
||||
|
||||
For the forecast of household consumption EOS provides a standard load curve for an
|
||||
average day based on annual household consumption that you can fetch via API. This data
|
||||
was compiled based on data from several households and provides an initial usable basis.
|
||||
Alternatively your own collected historical data could be used to reflect your personal
|
||||
consumption behaviour.
|
||||
|
||||
## Simulation Results
|
||||
|
||||
Based on the input data, the EOS uses a genetic algorithm to create a cost-optimized
|
||||
schedule for the coming hours from numerous simulations of the overall system.
|
||||
|
||||
The plan created contains for each of the coming hours
|
||||
|
||||
- Control information
|
||||
- whether and with what power the battery storage should be charged from the grid
|
||||
- when the battery storage should be charged via the PV system
|
||||
- whether discharging the battery storage is permitted or not
|
||||
- when and with what power the electric car should be charged
|
||||
- when a household appliance should be activated
|
||||
- Energy history information
|
||||
- Total load of the house
|
||||
- Grid consumption
|
||||
- Feed-in
|
||||
- Load of the planned household appliances
|
||||
- Charge level of the battery storage
|
||||
- Charge level of the electric car
|
||||
- Active losses
|
||||
- Cost information
|
||||
- Revenue per hour (when fed into the grid)
|
||||
- Total costs per hour (when drawn from the grid)
|
||||
- Overall balance (revenue-costs)
|
||||
- Cost development
|
||||
|
||||
If required, the simulation result can also be created and downloaded in graphical
|
||||
form as a PDF from EOS.
|
||||
|
||||
## Integration
|
||||
|
||||
The Akkudoktor EOS can be integrated into a wide variety of systems with a variety
|
||||
of components.
|
||||
|
||||

|
||||
|
||||
However, the components are not integrated by the EOS itself, but must be intergrated by
|
||||
the user using an integration solution and currently requires some effort and technical
|
||||
know-how.
|
||||
|
||||
Any [integration](#integration-page) solution that can act as an intermediary between the
|
||||
components and the REST API of EOS can be used. One possible solution that enables the
|
||||
integration of components and EOS is Node-RED. Another solution could be Home Assistant
|
||||
usings its built in features.
|
||||
|
||||
Access to the data and functions of the components can be done in a variety of ways.
|
||||
Node-RED offers a large number of types of nodes that allow access via the protocols
|
||||
commonly used in this area, such as Modbus or MQTT. Access to any existing databases,
|
||||
such as InfluxDB or PostgreSQL, is also possible via nodes provided by Node-RED.
|
||||
|
||||
It becomes easier if a smart home solution like Homa Assistant, openHAB or ioBroker or
|
||||
solutions such as evcc or openWB are already in use. In this case, these smart home
|
||||
solutions already take over the technical integration and communication with the components
|
||||
at a technical level and Node-RED offers nodes for accessing these solutions, so that the
|
||||
corresponding sources can be easily integrated into a flow.
|
||||
|
||||
In Home Assistant you could use an automation to prepare the input payload for EOS and
|
||||
then use the RESTful integration to call EOS. Based on this concept there is already a
|
||||
home assistand add-on created by [Duetting](#duetting-solution).
|
||||
|
||||
The plan created by EOS must also be executed via the chosen integration solution,
|
||||
with the respective devices receiving their instructions according to the plan.
|
||||
|
||||
## Limitations
|
||||
|
||||
The plan calculated by EOS is cost-optimized due to the genetic algorithm used, but not
|
||||
necessarily cost-optimal, since genetic algorithms do not always find the global optimum,
|
||||
but usually find good local optima very quickly in a large solution space.
|
||||
|
||||
## Links
|
||||
|
||||
- [German Video explaining the basic concept and installation process for the early version of EOS (YouTube)](https://www.youtube.com/live/ftQULW4-1ts?si=oDdBBifCpUmiCXaY)
|
||||
- [German Forum of Akkudoktor EOS](https://akkudoktor.net/c/der-akkudoktor/eos)
|
||||
- [Akkudoktor-EOS GitHub Repository](https://github.com/Akkudoktor-EOS/EOS)
|
||||
- [Latest EOS Documentation](https://akkudoktor-eos.readthedocs.io/en/latest/)
|
@@ -5,9 +5,9 @@
|
||||
Measurements are utilized to refine predictions using real data from your system, thereby enhancing
|
||||
accuracy.
|
||||
|
||||
- **Household Load Measurement**
|
||||
- **Grid Export Measurement**
|
||||
- **Grid Import Measurement**
|
||||
- Household Load Measurement
|
||||
- Grid Export Measurement
|
||||
- Grid Import Measurement
|
||||
|
||||
## Storing Measurements
|
||||
|
||||
|
@@ -2,7 +2,199 @@
|
||||
|
||||
# Optimization
|
||||
|
||||
:::{admonition} Todo
|
||||
:class: note
|
||||
Describe optimization.
|
||||
:::
|
||||
## Introduction
|
||||
|
||||
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
|
||||
```
|
||||
|
@@ -1,14 +1,15 @@
|
||||
% SPDX-License-Identifier: Apache-2.0
|
||||
(prediction-page)=
|
||||
|
||||
# Predictions
|
||||
|
||||
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:
|
||||
|
||||
- **Household Load Prediction**
|
||||
- **Electricity Price Prediction**
|
||||
- **PV Power Prediction**
|
||||
- **Weather Prediction**
|
||||
- Household Load Prediction
|
||||
- Electricity Price Prediction
|
||||
- PV Power Prediction
|
||||
- Weather Prediction
|
||||
|
||||
## Storing Predictions
|
||||
|
||||
@@ -56,13 +57,15 @@ A dictionary with the following structure:
|
||||
#### 2. DateTimeDataFrame
|
||||
|
||||
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.
|
||||
|
||||
#### 3. DateTimeSeries
|
||||
|
||||
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
|
||||
|
||||
@@ -196,13 +199,20 @@ Configuration options:
|
||||
- `latitude`: Latitude in decimal degrees, between -90 and 90, north is positive (ISO 19115) (°)"
|
||||
- `longitude`: Longitude in decimal degrees, within -180 to 180 (°)
|
||||
- `pvforecast<0..5>_surface_tilt`: Tilt angle from horizontal plane. Ignored for two-axis tracking.
|
||||
- `pvforecast<0..5>_surface_azimuth`: Orientation (azimuth angle) of the (fixed) plane. Clockwise from north (north=0, east=90, south=180, west=270).
|
||||
- `pvforecast<0..5>_surface_azimuth`: Orientation (azimuth angle) of the (fixed) plane.
|
||||
Clockwise from north (north=0, east=90, south=180, west=270).
|
||||
- `pvforecast<0..5>_userhorizon`: Elevation of horizon in degrees, at equally spaced azimuth clockwise from north.
|
||||
- `pvforecast<0..5>_peakpower`: Nominal power of PV system in kW.
|
||||
- `pvforecast<0..5>_pvtechchoice`: PV technology. One of 'crystSi', 'CIS', 'CdTe', 'Unknown'.
|
||||
- `pvforecast<0..5>_mountingplace`: Type of mounting for PV system. Options are 'free' for free-standing and 'building' for building-integrated.
|
||||
- `pvforecast<0..5>_mountingplace`: Type of mounting for PV system. Options are 'free' for free-standing
|
||||
and 'building' for building-integrated.
|
||||
- `pvforecast<0..5>_loss`: Sum of PV system losses in percent
|
||||
- `pvforecast<0..5>_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.
|
||||
- `pvforecast<0..5>_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.
|
||||
- `pvforecast<0..5>_optimal_surface_tilt`: Calculate the optimum tilt angle. Ignored for two-axis tracking.
|
||||
- `pvforecast<0..5>_optimalangles`: Calculate the optimum tilt and azimuth angles. Ignored for two-axis tracking.
|
||||
- `pvforecast<0..5>_albedo`: Proportion of the light hitting the ground that it reflects back.
|
||||
@@ -214,39 +224,76 @@ Configuration options:
|
||||
- `pvforecastimport_file_path`: Path to the file to import PV forecast data from.
|
||||
- `pvforecastimport_json`: JSON string, dictionary of PV forecast value lists.
|
||||
|
||||
------
|
||||
---
|
||||
|
||||
Some of the 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.
|
||||
Some of the 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**:
|
||||
|
||||
- `pvforecast<0..5>_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.
|
||||
|
||||
- `pvforecast<0..5>_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.
|
||||
|
||||
- `pvforecast<0..5>_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.
|
||||
|
||||
- `pvforecast<0..5>_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.
|
||||
|
||||
- `pvforecast<0..5>_userhorizon`
|
||||
|
||||
@@ -258,9 +305,10 @@ represent equal angular distance around the horizon. For instance, if you have 3
|
||||
point is due north, the next is 10 degrees east of north, and so on, until the last point, 10
|
||||
degrees west of north.
|
||||
|
||||
------
|
||||
---
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
@@ -273,7 +321,7 @@ Tilt angle from horizontal plane.
|
||||
Orientation (azimuth angle) of the (fixed) plane. Clockwise from north (north=0, east=90, south=180,
|
||||
west=270). This is offset 180 degrees from the convention used by PVGIS.
|
||||
|
||||
------
|
||||
---
|
||||
|
||||
### PVForecastAkkudoktor Provider
|
||||
|
||||
@@ -288,7 +336,8 @@ The following general configuration options of the PV system must be set:
|
||||
For each plane `<0..5>` of the PV system the following configuration options must be set:
|
||||
|
||||
- `pvforecast<0..5>_surface_tilt`: Tilt angle from horizontal plane. Ignored for two-axis tracking.
|
||||
- `pvforecast<0..5>_surface_azimuth`: Orientation (azimuth angle) of the (fixed) plane. Clockwise from north (north=0, east=90, south=180, west=270).
|
||||
- `pvforecast<0..5>_surface_azimuth`: Orientation (azimuth angle) of the (fixed) plane.
|
||||
Clockwise from north (north=0, east=90, south=180, west=270).
|
||||
- `pvforecast<0..5>_userhorizon`: Elevation of horizon in degrees, at equally spaced azimuth clockwise from north.
|
||||
- `pvforecast<0..5>_inverter_paco`: AC power rating of the inverter. [W]
|
||||
- `pvforecast<0..5>_peakpower`: Nominal power of PV system in kW.
|
||||
@@ -370,8 +419,8 @@ Configuration options:
|
||||
|
||||
- `weather_provider`: Load provider id of provider to be used.
|
||||
|
||||
- `BrightSky`: Retrieves from https://api.brightsky.dev.
|
||||
- `ClearOutside`: Retrieves from https://clearoutside.com/forecast.
|
||||
- `BrightSky`: Retrieves from [BrightSky](https://api.brightsky.dev).
|
||||
- `ClearOutside`: Retrieves from [ClearOutside](https://clearoutside.com/forecast).
|
||||
- `LoadImport`: Imports from a file or JSON string.
|
||||
|
||||
- `weatherimport_file_path`: Path to the file to import weatherforecast data from.
|
||||
|
@@ -19,6 +19,7 @@ Install the dependencies in a virtual environment:
|
||||
|
||||
python -m venv .venv
|
||||
.venv\Scripts\pip install -r requirements.txt
|
||||
.venv\Scripts\pip install -e .
|
||||
|
||||
.. tab:: Linux
|
||||
|
||||
@@ -26,6 +27,7 @@ Install the dependencies in a virtual environment:
|
||||
|
||||
python -m venv .venv
|
||||
.venv/bin/pip install -r requirements.txt
|
||||
.venv/bin/pip install -e .
|
||||
|
||||
```
|
||||
|
||||
@@ -73,37 +75,53 @@ This project uses the `EOS.config.json` file to manage configuration settings.
|
||||
|
||||
### 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
|
||||
|
||||
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 `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`.
|
||||
- If the directory specified by `EOS_DIR` contains an existing `EOS.config.json` file, the
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
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.
|
||||
|
@@ -8,12 +8,32 @@
|
||||
|
||||
```{toctree}
|
||||
:maxdepth: 2
|
||||
:caption: 'Contents:'
|
||||
:caption: Overview
|
||||
|
||||
akkudoktoreos/introduction.md
|
||||
|
||||
```
|
||||
|
||||
```{toctree}
|
||||
:maxdepth: 2
|
||||
:caption: Tutorials
|
||||
|
||||
welcome.md
|
||||
akkudoktoreos/about.md
|
||||
develop/getting_started.md
|
||||
|
||||
```
|
||||
|
||||
```{toctree}
|
||||
:maxdepth: 2
|
||||
:caption: How-To Guides
|
||||
|
||||
develop/CONTRIBUTING.md
|
||||
|
||||
```
|
||||
|
||||
```{toctree}
|
||||
:maxdepth: 2
|
||||
:caption: Reference
|
||||
|
||||
akkudoktoreos/architecture.md
|
||||
akkudoktoreos/configuration.md
|
||||
akkudoktoreos/optimization.md
|
||||
@@ -22,9 +42,10 @@ akkudoktoreos/measurement.md
|
||||
akkudoktoreos/integration.md
|
||||
akkudoktoreos/serverapi.md
|
||||
akkudoktoreos/api.rst
|
||||
|
||||
```
|
||||
|
||||
# Indices and tables
|
||||
## Indices and tables
|
||||
|
||||
- {ref}`genindex`
|
||||
- {ref}`modindex`
|
||||
|
20
docs/pymarkdown.json
Normal file
20
docs/pymarkdown.json
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,12 +1,12 @@
|
||||
% 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
|
||||
[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
|
||||
[Sphinx](https://www.sphinx-doc.org) and will someday land on
|
||||
[Read the Docs](https://akkudoktoreos.readthedocs.io/en/latest/index.html).
|
||||
[Sphinx](https://www.sphinx-doc.org) and is available on
|
||||
[Read the Docs](https://akkudoktor-eos.readthedocs.io/en/latest/).
|
||||
|
||||
You can contribute to EOS's documentation by opening
|
||||
[GitHub issues](https://github.com/Akkudoktor-EOS/EOS/issues)
|
||||
|
@@ -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."
|
||||
readme = "README.md"
|
||||
license = {file = "LICENSE"}
|
||||
requires-python = ">=3.10"
|
||||
requires-python = ">=3.11"
|
||||
classifiers = [
|
||||
"Development Status :: 3 - Alpha",
|
||||
"Programming Language :: Python :: 3",
|
||||
|
@@ -1,14 +1,14 @@
|
||||
-r requirements.txt
|
||||
gitpython==3.1.44
|
||||
linkify-it-py==2.0.3
|
||||
myst-parser==4.0.0
|
||||
sphinx==8.1.3
|
||||
myst-parser==4.0.1
|
||||
sphinx==8.2.3
|
||||
sphinx_rtd_theme==3.0.2
|
||||
sphinx-tabs==3.4.7
|
||||
pytest==8.3.4
|
||||
pytest==8.3.5
|
||||
pytest-cov==6.0.0
|
||||
pytest-xprocess==1.0.2
|
||||
pre-commit
|
||||
mypy==1.13.0
|
||||
types-requests==2.32.0.20241016
|
||||
pandas-stubs==2.2.3.241126
|
||||
mypy==1.15.0
|
||||
types-requests==2.32.0.20250306
|
||||
pandas-stubs==2.2.3.250308
|
||||
|
@@ -1,16 +1,16 @@
|
||||
numpy==2.2.2
|
||||
numpydantic==1.6.4
|
||||
matplotlib==3.10.0
|
||||
fastapi[standard]==0.115.6
|
||||
python-fasthtml==0.12.0
|
||||
numpy==2.2.4
|
||||
numpydantic==1.6.8
|
||||
matplotlib==3.10.1
|
||||
fastapi[standard]==0.115.11
|
||||
python-fasthtml==0.12.4
|
||||
uvicorn==0.34.0
|
||||
scikit-learn==1.6.1
|
||||
timezonefinder==6.5.7
|
||||
timezonefinder==6.5.8
|
||||
deap==1.4.2
|
||||
requests==2.32.3
|
||||
pandas==2.2.3
|
||||
pendulum==3.0.0
|
||||
platformdirs==4.3.6
|
||||
pvlib==0.11.2
|
||||
pydantic==2.10.5
|
||||
platformdirs==4.3.7
|
||||
pvlib==0.12.0
|
||||
pydantic==2.10.6
|
||||
statsmodels==0.14.4
|
||||
|
@@ -1,4 +1,4 @@
|
||||
from typing import Any, ClassVar, Dict, Optional, Union
|
||||
from typing import Any, ClassVar, Optional
|
||||
|
||||
import numpy as np
|
||||
from numpydantic import NDArray, Shape
|
||||
@@ -186,7 +186,7 @@ class EnergieManagementSystem(SingletonMixin, ConfigMixin, PredictionMixin, Pyda
|
||||
len(self.load_energy_array), parameters.einspeiseverguetung_euro_pro_wh, float
|
||||
)
|
||||
)
|
||||
if inverter is not None:
|
||||
if inverter:
|
||||
self.battery = inverter.battery
|
||||
else:
|
||||
self.battery = None
|
||||
@@ -198,7 +198,7 @@ class EnergieManagementSystem(SingletonMixin, ConfigMixin, PredictionMixin, Pyda
|
||||
self.ev_charge_hours = np.full(self.config.prediction_hours, 0.0)
|
||||
|
||||
def set_akku_discharge_hours(self, ds: np.ndarray) -> None:
|
||||
if self.battery is not None:
|
||||
if self.battery:
|
||||
self.battery.set_discharge_per_hour(ds)
|
||||
|
||||
def set_akku_ac_charge_hours(self, ds: np.ndarray) -> None:
|
||||
@@ -211,7 +211,7 @@ class EnergieManagementSystem(SingletonMixin, ConfigMixin, PredictionMixin, Pyda
|
||||
self.ev_charge_hours = ds
|
||||
|
||||
def set_home_appliance_start(self, ds: int, global_start_hour: int = 0) -> None:
|
||||
if self.home_appliance is not None:
|
||||
if self.home_appliance:
|
||||
self.home_appliance.set_starting_time(ds, global_start_hour=global_start_hour)
|
||||
|
||||
def reset(self) -> None:
|
||||
@@ -276,53 +276,50 @@ class EnergieManagementSystem(SingletonMixin, ConfigMixin, PredictionMixin, Pyda
|
||||
return self.simulate(start_hour)
|
||||
|
||||
def simulate(self, start_hour: int) -> dict[str, Any]:
|
||||
"""hour.
|
||||
"""Simulate energy usage and costs for the given start hour.
|
||||
|
||||
akku_soc_pro_stunde begin of the hour, initial hour state!
|
||||
last_wh_pro_stunde integral of last hour (end state)
|
||||
last_wh_pro_stunde integral of last hour (end state)
|
||||
"""
|
||||
# Check for simulation integrity
|
||||
missing_data = []
|
||||
|
||||
if self.load_energy_array is None:
|
||||
missing_data.append("Load Curve")
|
||||
if self.pv_prediction_wh is None:
|
||||
missing_data.append("PV Forecast")
|
||||
if self.elect_price_hourly is None:
|
||||
missing_data.append("Electricity Price")
|
||||
if self.ev_charge_hours is None:
|
||||
missing_data.append("EV Charge Hours")
|
||||
if self.ac_charge_hours is None:
|
||||
missing_data.append("AC Charge Hours")
|
||||
if self.dc_charge_hours is None:
|
||||
missing_data.append("DC Charge Hours")
|
||||
if self.elect_revenue_per_hour_arr is None:
|
||||
missing_data.append("Feed-in Tariff")
|
||||
required_attrs = [
|
||||
"load_energy_array",
|
||||
"pv_prediction_wh",
|
||||
"elect_price_hourly",
|
||||
"ev_charge_hours",
|
||||
"ac_charge_hours",
|
||||
"dc_charge_hours",
|
||||
"elect_revenue_per_hour_arr",
|
||||
]
|
||||
missing_data = [
|
||||
attr.replace("_", " ").title() for attr in required_attrs if getattr(self, attr) is None
|
||||
]
|
||||
|
||||
if missing_data:
|
||||
error_msg = "Mandatory data missing - " + ", ".join(missing_data)
|
||||
logger.error(error_msg)
|
||||
raise ValueError(error_msg)
|
||||
else:
|
||||
# make mypy happy
|
||||
assert self.load_energy_array is not None
|
||||
assert self.pv_prediction_wh is not None
|
||||
assert self.elect_price_hourly is not None
|
||||
assert self.ev_charge_hours is not None
|
||||
assert self.ac_charge_hours is not None
|
||||
assert self.dc_charge_hours is not None
|
||||
assert self.elect_revenue_per_hour_arr is not None
|
||||
logger.error("Mandatory data missing - %s", ", ".join(missing_data))
|
||||
raise ValueError(f"Mandatory data missing: {', '.join(missing_data)}")
|
||||
|
||||
load_energy_array = self.load_energy_array
|
||||
# Pre-fetch data
|
||||
load_energy_array = np.array(self.load_energy_array)
|
||||
pv_prediction_wh = np.array(self.pv_prediction_wh)
|
||||
elect_price_hourly = np.array(self.elect_price_hourly)
|
||||
ev_charge_hours = np.array(self.ev_charge_hours)
|
||||
ac_charge_hours = np.array(self.ac_charge_hours)
|
||||
dc_charge_hours = np.array(self.dc_charge_hours)
|
||||
elect_revenue_per_hour_arr = np.array(self.elect_revenue_per_hour_arr)
|
||||
|
||||
if not (
|
||||
len(load_energy_array) == len(self.pv_prediction_wh) == len(self.elect_price_hourly)
|
||||
):
|
||||
error_msg = f"Array sizes do not match: Load Curve = {len(load_energy_array)}, PV Forecast = {len(self.pv_prediction_wh)}, Electricity Price = {len(self.elect_price_hourly)}"
|
||||
# Fetch objects
|
||||
battery = self.battery
|
||||
assert battery # to please mypy
|
||||
ev = self.ev
|
||||
home_appliance = self.home_appliance
|
||||
inverter = self.inverter
|
||||
|
||||
if not (len(load_energy_array) == len(pv_prediction_wh) == len(elect_price_hourly)):
|
||||
error_msg = f"Array sizes do not match: Load Curve = {len(load_energy_array)}, PV Forecast = {len(pv_prediction_wh)}, Electricity Price = {len(elect_price_hourly)}"
|
||||
logger.error(error_msg)
|
||||
raise ValueError(error_msg)
|
||||
|
||||
# Optimized total hours calculation
|
||||
end_hour = len(load_energy_array)
|
||||
total_hours = end_hour - start_hour
|
||||
|
||||
@@ -332,116 +329,110 @@ class EnergieManagementSystem(SingletonMixin, ConfigMixin, PredictionMixin, Pyda
|
||||
consumption_energy_per_hour = np.full((total_hours), np.nan)
|
||||
costs_per_hour = np.full((total_hours), np.nan)
|
||||
revenue_per_hour = np.full((total_hours), np.nan)
|
||||
soc_per_hour = np.full((total_hours), np.nan) # Hour End State
|
||||
soc_per_hour = np.full((total_hours), np.nan)
|
||||
soc_ev_per_hour = np.full((total_hours), np.nan)
|
||||
losses_wh_per_hour = np.full((total_hours), np.nan)
|
||||
home_appliance_wh_per_hour = np.full((total_hours), np.nan)
|
||||
electricity_price_per_hour = np.full((total_hours), np.nan)
|
||||
|
||||
# Set initial state
|
||||
if self.battery:
|
||||
soc_per_hour[0] = self.battery.current_soc_percentage()
|
||||
if self.ev:
|
||||
soc_ev_per_hour[0] = self.ev.current_soc_percentage()
|
||||
soc_per_hour[0] = battery.current_soc_percentage()
|
||||
if ev:
|
||||
soc_ev_per_hour[0] = ev.current_soc_percentage()
|
||||
|
||||
for hour in range(start_hour, end_hour):
|
||||
hour_since_now = hour - start_hour
|
||||
hour_idx = hour - start_hour
|
||||
|
||||
# save begin states
|
||||
if self.battery:
|
||||
soc_per_hour[hour_since_now] = self.battery.current_soc_percentage()
|
||||
else:
|
||||
soc_per_hour[hour_since_now] = 0.0
|
||||
if self.ev:
|
||||
soc_ev_per_hour[hour_since_now] = self.ev.current_soc_percentage()
|
||||
soc_per_hour[hour_idx] = battery.current_soc_percentage()
|
||||
|
||||
if ev:
|
||||
soc_ev_per_hour[hour_idx] = ev.current_soc_percentage()
|
||||
|
||||
# Accumulate loads and PV generation
|
||||
consumption = self.load_energy_array[hour]
|
||||
losses_wh_per_hour[hour_since_now] = 0.0
|
||||
consumption = load_energy_array[hour]
|
||||
losses_wh_per_hour[hour_idx] = 0.0
|
||||
|
||||
# Home appliances
|
||||
if self.home_appliance:
|
||||
ha_load = self.home_appliance.get_load_for_hour(hour)
|
||||
if home_appliance:
|
||||
ha_load = home_appliance.get_load_for_hour(hour)
|
||||
consumption += ha_load
|
||||
home_appliance_wh_per_hour[hour_since_now] = ha_load
|
||||
home_appliance_wh_per_hour[hour_idx] = ha_load
|
||||
|
||||
# E-Auto handling
|
||||
if self.ev:
|
||||
if self.ev_charge_hours[hour] > 0:
|
||||
loaded_energy_ev, verluste_eauto = self.ev.charge_energy(
|
||||
None, hour, relative_power=self.ev_charge_hours[hour]
|
||||
)
|
||||
consumption += loaded_energy_ev
|
||||
losses_wh_per_hour[hour_since_now] += verluste_eauto
|
||||
if ev and ev_charge_hours[hour] > 0:
|
||||
loaded_energy_ev, verluste_eauto = ev.charge_energy(
|
||||
None, hour, relative_power=ev_charge_hours[hour]
|
||||
)
|
||||
consumption += loaded_energy_ev
|
||||
losses_wh_per_hour[hour_idx] += verluste_eauto
|
||||
|
||||
# Process inverter logic
|
||||
energy_feedin_grid_actual, energy_consumption_grid_actual, losses, eigenverbrauch = (
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
energy_feedin_grid_actual = energy_consumption_grid_actual = losses = eigenverbrauch = (
|
||||
0.0
|
||||
)
|
||||
if self.battery:
|
||||
self.battery.set_charge_allowed_for_hour(self.dc_charge_hours[hour], hour)
|
||||
if self.inverter:
|
||||
energy_produced = self.pv_prediction_wh[hour]
|
||||
|
||||
hour_ac_charge = ac_charge_hours[hour]
|
||||
hour_dc_charge = dc_charge_hours[hour]
|
||||
hourly_electricity_price = elect_price_hourly[hour]
|
||||
hourly_energy_revenue = elect_revenue_per_hour_arr[hour]
|
||||
|
||||
battery.set_charge_allowed_for_hour(hour_dc_charge, hour)
|
||||
|
||||
if inverter:
|
||||
energy_produced = pv_prediction_wh[hour]
|
||||
(
|
||||
energy_feedin_grid_actual,
|
||||
energy_consumption_grid_actual,
|
||||
losses,
|
||||
eigenverbrauch,
|
||||
) = self.inverter.process_energy(energy_produced, consumption, hour)
|
||||
) = inverter.process_energy(energy_produced, consumption, hour)
|
||||
|
||||
# AC PV Battery Charge
|
||||
if self.battery and self.ac_charge_hours[hour] > 0.0:
|
||||
self.battery.set_charge_allowed_for_hour(1, hour)
|
||||
battery_charged_energy_actual, battery_losses_actual = self.battery.charge_energy(
|
||||
None, hour, relative_power=self.ac_charge_hours[hour]
|
||||
if hour_ac_charge > 0.0:
|
||||
battery.set_charge_allowed_for_hour(1, hour)
|
||||
battery_charged_energy_actual, battery_losses_actual = battery.charge_energy(
|
||||
None, hour, relative_power=hour_ac_charge
|
||||
)
|
||||
# print(hour, " ", battery_charged_energy_actual, " ",self.ac_charge_hours[hour]," ",self.battery.current_soc_percentage())
|
||||
consumption += battery_charged_energy_actual
|
||||
consumption += battery_losses_actual
|
||||
energy_consumption_grid_actual += battery_charged_energy_actual
|
||||
energy_consumption_grid_actual += battery_losses_actual
|
||||
losses_wh_per_hour[hour_since_now] += battery_losses_actual
|
||||
|
||||
feedin_energy_per_hour[hour_since_now] = energy_feedin_grid_actual
|
||||
consumption_energy_per_hour[hour_since_now] = energy_consumption_grid_actual
|
||||
losses_wh_per_hour[hour_since_now] += losses
|
||||
loads_energy_per_hour[hour_since_now] = consumption
|
||||
electricity_price_per_hour[hour_since_now] = self.elect_price_hourly[hour]
|
||||
total_battery_energy = battery_charged_energy_actual + battery_losses_actual
|
||||
consumption += total_battery_energy
|
||||
energy_consumption_grid_actual += total_battery_energy
|
||||
losses_wh_per_hour[hour_idx] += battery_losses_actual
|
||||
|
||||
# Update hourly arrays
|
||||
feedin_energy_per_hour[hour_idx] = energy_feedin_grid_actual
|
||||
consumption_energy_per_hour[hour_idx] = energy_consumption_grid_actual
|
||||
losses_wh_per_hour[hour_idx] += losses
|
||||
loads_energy_per_hour[hour_idx] = consumption
|
||||
electricity_price_per_hour[hour_idx] = hourly_electricity_price
|
||||
|
||||
# Financial calculations
|
||||
costs_per_hour[hour_since_now] = (
|
||||
energy_consumption_grid_actual * self.elect_price_hourly[hour]
|
||||
)
|
||||
revenue_per_hour[hour_since_now] = (
|
||||
energy_feedin_grid_actual * self.elect_revenue_per_hour_arr[hour]
|
||||
)
|
||||
costs_per_hour[hour_idx] = energy_consumption_grid_actual * hourly_electricity_price
|
||||
revenue_per_hour[hour_idx] = energy_feedin_grid_actual * hourly_energy_revenue
|
||||
|
||||
# Total cost and return
|
||||
gesamtkosten_euro = np.nansum(costs_per_hour) - np.nansum(revenue_per_hour)
|
||||
total_cost = np.nansum(costs_per_hour)
|
||||
total_losses = np.nansum(losses_wh_per_hour)
|
||||
total_revenue = np.nansum(revenue_per_hour)
|
||||
|
||||
# Prepare output dictionary
|
||||
out: Dict[str, Union[np.ndarray, float]] = {
|
||||
return {
|
||||
"Last_Wh_pro_Stunde": loads_energy_per_hour,
|
||||
"Netzeinspeisung_Wh_pro_Stunde": feedin_energy_per_hour,
|
||||
"Netzbezug_Wh_pro_Stunde": consumption_energy_per_hour,
|
||||
"Kosten_Euro_pro_Stunde": costs_per_hour,
|
||||
"akku_soc_pro_stunde": soc_per_hour,
|
||||
"Einnahmen_Euro_pro_Stunde": revenue_per_hour,
|
||||
"Gesamtbilanz_Euro": gesamtkosten_euro,
|
||||
"Gesamtbilanz_Euro": total_cost - total_revenue,
|
||||
"EAuto_SoC_pro_Stunde": soc_ev_per_hour,
|
||||
"Gesamteinnahmen_Euro": np.nansum(revenue_per_hour),
|
||||
"Gesamtkosten_Euro": np.nansum(costs_per_hour),
|
||||
"Gesamteinnahmen_Euro": total_revenue,
|
||||
"Gesamtkosten_Euro": total_cost,
|
||||
"Verluste_Pro_Stunde": losses_wh_per_hour,
|
||||
"Gesamt_Verluste": np.nansum(losses_wh_per_hour),
|
||||
"Gesamt_Verluste": total_losses,
|
||||
"Home_appliance_wh_per_hour": home_appliance_wh_per_hour,
|
||||
"Electricity_price": electricity_price_per_hour,
|
||||
}
|
||||
|
||||
return out
|
||||
|
||||
|
||||
# Initialize the Energy Management System, it is a singleton.
|
||||
ems = EnergieManagementSystem()
|
||||
|
@@ -223,7 +223,6 @@ app = FastAPI(
|
||||
"url": "https://www.apache.org/licenses/LICENSE-2.0.html",
|
||||
},
|
||||
lifespan=lifespan,
|
||||
root_path=str(Path(__file__).parent),
|
||||
)
|
||||
|
||||
|
||||
@@ -882,9 +881,13 @@ async def proxy_put(request: Request, path: str) -> Response:
|
||||
|
||||
|
||||
async def proxy(request: Request, path: str) -> Union[Response | RedirectResponse | HTMLResponse]:
|
||||
if config_eos.server_eosdash_host and config_eos.server_eosdash_port:
|
||||
# Make hostname Windows friendly
|
||||
host = str(config_eos.server_eosdash_host)
|
||||
if host == "0.0.0.0" and os.name == "nt":
|
||||
host = "localhost"
|
||||
if host and config_eos.server_eosdash_port:
|
||||
# Proxy to EOSdash server
|
||||
url = f"http://{config_eos.server_eosdash_host}:{config_eos.server_eosdash_port}/{path}"
|
||||
url = f"http://{host}:{config_eos.server_eosdash_port}/{path}"
|
||||
headers = dict(request.headers)
|
||||
|
||||
data = await request.body()
|
||||
|
@@ -24,7 +24,7 @@ for field_name in config_eos.model_fields:
|
||||
configs.append(config)
|
||||
|
||||
|
||||
app = FastHTML()
|
||||
app = FastHTML(secret_key=os.getenv("EOS_SERVER__EOSDASH_SESSKEY"))
|
||||
rt = app.route
|
||||
|
||||
|
||||
|
@@ -1,5 +1,6 @@
|
||||
"""Server Module."""
|
||||
|
||||
import os
|
||||
from typing import Optional
|
||||
|
||||
from pydantic import Field, IPvAnyAddress, field_validator
|
||||
@@ -10,6 +11,12 @@ from akkudoktoreos.core.logging import get_logger
|
||||
logger = get_logger(__name__)
|
||||
|
||||
|
||||
def get_default_host() -> str:
|
||||
if os.name == "nt":
|
||||
return "127.0.0.1"
|
||||
return "0.0.0.0"
|
||||
|
||||
|
||||
class ServerCommonSettings(SettingsBaseModel):
|
||||
"""Common server settings.
|
||||
|
||||
@@ -18,7 +25,7 @@ class ServerCommonSettings(SettingsBaseModel):
|
||||
"""
|
||||
|
||||
server_eos_host: Optional[IPvAnyAddress] = Field(
|
||||
default="0.0.0.0", description="EOS server IP address."
|
||||
default=get_default_host(), description="EOS server IP address."
|
||||
)
|
||||
server_eos_port: Optional[int] = Field(default=8503, description="EOS server IP port number.")
|
||||
server_eos_verbose: Optional[bool] = Field(default=False, description="Enable debug output")
|
||||
@@ -26,7 +33,7 @@ class ServerCommonSettings(SettingsBaseModel):
|
||||
default=True, description="EOS server to start EOSdash server."
|
||||
)
|
||||
server_eosdash_host: Optional[IPvAnyAddress] = Field(
|
||||
default="0.0.0.0", description="EOSdash server IP address."
|
||||
default=get_default_host(), description="EOSdash server IP address."
|
||||
)
|
||||
server_eosdash_port: Optional[int] = Field(
|
||||
default=8504, description="EOSdash server IP port number."
|
||||
|
@@ -13,6 +13,7 @@ import pendulum
|
||||
from matplotlib.backends.backend_pdf import PdfPages
|
||||
|
||||
from akkudoktoreos.core.coreabc import ConfigMixin
|
||||
from akkudoktoreos.core.ems import EnergieManagementSystem
|
||||
from akkudoktoreos.core.logging import get_logger
|
||||
from akkudoktoreos.optimization.genetic import OptimizationParameters
|
||||
from akkudoktoreos.utils.datetimeutil import to_datetime
|
||||
@@ -147,14 +148,16 @@ class VisualizationReport(ConfigMixin):
|
||||
|
||||
# Format the time axis
|
||||
plt.gca().xaxis.set_major_formatter(
|
||||
mdates.DateFormatter("%Y-%m-%d")
|
||||
mdates.DateFormatter("%Y-%m-%d", tz=self.config.timezone)
|
||||
) # Show date and time
|
||||
plt.gca().xaxis.set_major_locator(
|
||||
mdates.DayLocator(interval=1, tz=None)
|
||||
mdates.DayLocator(interval=1, tz=self.config.timezone)
|
||||
) # Major ticks every day
|
||||
plt.gca().xaxis.set_minor_locator(mdates.HourLocator(interval=3, tz=None))
|
||||
plt.gca().xaxis.set_minor_locator(
|
||||
mdates.HourLocator(interval=2, tz=self.config.timezone)
|
||||
)
|
||||
# Minor ticks every 6 hours
|
||||
plt.gca().xaxis.set_minor_formatter(mdates.DateFormatter("%H"))
|
||||
plt.gca().xaxis.set_minor_formatter(mdates.DateFormatter("%H", tz=self.config.timezone))
|
||||
# plt.gcf().autofmt_xdate(rotation=45, which="major")
|
||||
# Auto-format the x-axis for readability
|
||||
|
||||
@@ -174,6 +177,7 @@ class VisualizationReport(ConfigMixin):
|
||||
|
||||
# Add vertical line for the current date if within the axis range
|
||||
current_time = pendulum.now(self.config.timezone)
|
||||
# current_time = pendulum.now().add(hours=1)
|
||||
if timestamps[0].subtract(hours=2) <= current_time <= timestamps[-1]:
|
||||
plt.axvline(current_time, color="r", linestyle="--", label="Now")
|
||||
plt.text(current_time, plt.ylim()[1], "Now", color="r", ha="center", va="bottom")
|
||||
@@ -187,8 +191,10 @@ class VisualizationReport(ConfigMixin):
|
||||
hours_since_start = [(t - timestamps[0]).total_seconds() / 3600 for t in timestamps]
|
||||
# ax2.set_xticks(timestamps[::48]) # Set ticks every 12 hours
|
||||
# ax2.set_xticklabels([f"{int(h)}" for h in hours_since_start[::48]])
|
||||
ax2.set_xticks(timestamps[:: len(timestamps) // 24]) # Select 10 evenly spaced ticks
|
||||
ax2.set_xticklabels([f"{int(h)}" for h in hours_since_start[:: len(timestamps) // 24]])
|
||||
# ax2.set_xticks(timestamps[:: len(timestamps) // 24]) # Select 10 evenly spaced ticks
|
||||
ax2.set_xticks(timestamps[:: len(timestamps) // 12]) # Select 10 evenly spaced ticks
|
||||
# ax2.set_xticklabels([f"{int(h)}" for h in hours_since_start[:: len(timestamps) // 24]])
|
||||
ax2.set_xticklabels([f"{int(h)}" for h in hours_since_start[:: len(timestamps) // 12]])
|
||||
if x2label:
|
||||
ax2.set_xlabel(x2label)
|
||||
|
||||
@@ -416,15 +422,17 @@ def prepare_visualize(
|
||||
parameters: OptimizationParameters,
|
||||
results: dict,
|
||||
filename: str = "visualization_results.pdf",
|
||||
start_hour: Optional[int] = 0,
|
||||
start_hour: int = 0,
|
||||
) -> None:
|
||||
report = VisualizationReport(filename)
|
||||
next_full_hour_date = pendulum.now(report.config.timezone).start_of("hour").add(hours=1)
|
||||
# next_full_hour_date = pendulum.now(report.config.timezone).start_of("day").add(hours=start_hour)
|
||||
# next_full_hour_date = to_datetime().set(minute=0, second=0, microsecond=0)
|
||||
next_full_hour_date = EnergieManagementSystem.set_start_datetime()
|
||||
# Group 1:
|
||||
report.create_line_chart_date(
|
||||
next_full_hour_date, # start_date
|
||||
next_full_hour_date,
|
||||
[
|
||||
parameters.ems.gesamtlast,
|
||||
parameters.ems.gesamtlast[start_hour:],
|
||||
],
|
||||
title="Load Profile",
|
||||
# xlabel="Hours", # not enough space
|
||||
@@ -432,9 +440,9 @@ def prepare_visualize(
|
||||
labels=["Total Load (Wh)"],
|
||||
)
|
||||
report.create_line_chart_date(
|
||||
next_full_hour_date, # start_date
|
||||
next_full_hour_date,
|
||||
[
|
||||
parameters.ems.pv_prognose_wh,
|
||||
parameters.ems.pv_prognose_wh[start_hour:],
|
||||
],
|
||||
title="PV Forecast",
|
||||
# xlabel="Hours", # not enough space
|
||||
@@ -442,8 +450,15 @@ def prepare_visualize(
|
||||
)
|
||||
|
||||
report.create_line_chart_date(
|
||||
next_full_hour_date, # start_date
|
||||
[np.full(len(parameters.ems.gesamtlast), parameters.ems.einspeiseverguetung_euro_pro_wh)],
|
||||
next_full_hour_date,
|
||||
[
|
||||
np.full(
|
||||
len(parameters.ems.gesamtlast) - start_hour,
|
||||
parameters.ems.einspeiseverguetung_euro_pro_wh[start_hour:]
|
||||
if isinstance(parameters.ems.einspeiseverguetung_euro_pro_wh, list)
|
||||
else parameters.ems.einspeiseverguetung_euro_pro_wh,
|
||||
)
|
||||
],
|
||||
title="Remuneration",
|
||||
# xlabel="Hours", # not enough space
|
||||
ylabel="€/Wh",
|
||||
@@ -451,9 +466,9 @@ def prepare_visualize(
|
||||
)
|
||||
if parameters.temperature_forecast:
|
||||
report.create_line_chart_date(
|
||||
next_full_hour_date, # start_date
|
||||
next_full_hour_date,
|
||||
[
|
||||
parameters.temperature_forecast,
|
||||
parameters.temperature_forecast[start_hour:],
|
||||
],
|
||||
title="Temperature Forecast",
|
||||
# xlabel="Hours", # not enough space
|
||||
@@ -502,21 +517,35 @@ def prepare_visualize(
|
||||
)
|
||||
report.create_line_chart_date(
|
||||
next_full_hour_date, # start_date
|
||||
[parameters.ems.strompreis_euro_pro_wh],
|
||||
[parameters.ems.strompreis_euro_pro_wh[start_hour:]],
|
||||
# title="Electricity Price", # not enough space
|
||||
# xlabel="Date", # not enough space
|
||||
ylabel="Electricity Price (€/Wh)",
|
||||
x2label=None, # not enough space
|
||||
)
|
||||
|
||||
labels = list(
|
||||
item
|
||||
for sublist in zip(
|
||||
list(str(i) for i in range(0, 23, 2)), list(str(" ") for i in range(0, 23, 2))
|
||||
)
|
||||
for item in sublist
|
||||
)
|
||||
labels = labels[start_hour:] + labels
|
||||
|
||||
report.create_bar_chart(
|
||||
list(str(i) for i in range(len(results["ac_charge"]))),
|
||||
[results["ac_charge"], results["dc_charge"], results["discharge_allowed"]],
|
||||
labels,
|
||||
[
|
||||
results["ac_charge"][start_hour:],
|
||||
results["dc_charge"][start_hour:],
|
||||
results["discharge_allowed"][start_hour:],
|
||||
],
|
||||
title="AC/DC Charging and Discharge Overview",
|
||||
ylabel="Relative Power (0-1) / Discharge (0 or 1)",
|
||||
label_names=["AC Charging (relative)", "DC Charging (relative)", "Discharge Allowed"],
|
||||
colors=["blue", "green", "red"],
|
||||
bottom=3,
|
||||
xlabels=labels,
|
||||
)
|
||||
report.finalize_group()
|
||||
|
||||
|
@@ -6,6 +6,7 @@ import pytest
|
||||
from akkudoktoreos.core.ems import (
|
||||
EnergieManagementSystem,
|
||||
EnergieManagementSystemParameters,
|
||||
SimulationResult,
|
||||
get_ems,
|
||||
)
|
||||
from akkudoktoreos.devices.battery import (
|
||||
@@ -182,6 +183,7 @@ def test_simulation(create_ems_instance):
|
||||
# Assertions to validate results
|
||||
assert result is not None, "Result should not be None"
|
||||
assert isinstance(result, dict), "Result should be a dictionary"
|
||||
assert SimulationResult(**result) is not None
|
||||
assert "Last_Wh_pro_Stunde" in result, "Result should contain 'Last_Wh_pro_Stunde'"
|
||||
|
||||
"""
|
||||
@@ -240,7 +242,7 @@ def test_simulation(create_ems_instance):
|
||||
|
||||
assert (
|
||||
abs(result["Netzeinspeisung_Wh_pro_Stunde"][10] - 3946.93) < 1e-3
|
||||
), "'Netzeinspeisung_Wh_pro_Stunde[11]' should be 4000."
|
||||
), "'Netzeinspeisung_Wh_pro_Stunde[11]' should be 3946.93."
|
||||
|
||||
assert (
|
||||
abs(result["Netzeinspeisung_Wh_pro_Stunde"][11] - 0.0) < 1e-3
|
||||
@@ -251,6 +253,78 @@ def test_simulation(create_ems_instance):
|
||||
), "'akku_soc_pro_stunde[20]' should be 10."
|
||||
assert (
|
||||
abs(result["Last_Wh_pro_Stunde"][20] - 6050.98) < 1e-3
|
||||
), "'Netzeinspeisung_Wh_pro_Stunde[11]' should be 0.0."
|
||||
), "'Last_Wh_pro_Stunde[20]' should be 6050.98."
|
||||
|
||||
print("All tests passed successfully.")
|
||||
|
||||
|
||||
def test_set_parameters(create_ems_instance):
|
||||
"""Test the set_parameters method of EnergieManagementSystem."""
|
||||
ems = create_ems_instance
|
||||
|
||||
# Check if parameters are set correctly
|
||||
assert ems.load_energy_array is not None, "load_energy_array should not be None"
|
||||
assert ems.pv_prediction_wh is not None, "pv_prediction_wh should not be None"
|
||||
assert ems.elect_price_hourly is not None, "elect_price_hourly should not be None"
|
||||
assert (
|
||||
ems.elect_revenue_per_hour_arr is not None
|
||||
), "elect_revenue_per_hour_arr should not be None"
|
||||
|
||||
|
||||
def test_set_akku_discharge_hours(create_ems_instance):
|
||||
"""Test the set_akku_discharge_hours method of EnergieManagementSystem."""
|
||||
ems = create_ems_instance
|
||||
discharge_hours = np.full(ems.config.prediction_hours, 1.0)
|
||||
ems.set_akku_discharge_hours(discharge_hours)
|
||||
assert np.array_equal(
|
||||
ems.battery.discharge_array, discharge_hours
|
||||
), "Discharge hours should be set correctly"
|
||||
|
||||
|
||||
def test_set_akku_ac_charge_hours(create_ems_instance):
|
||||
"""Test the set_akku_ac_charge_hours method of EnergieManagementSystem."""
|
||||
ems = create_ems_instance
|
||||
ac_charge_hours = np.full(ems.config.prediction_hours, 1.0)
|
||||
ems.set_akku_ac_charge_hours(ac_charge_hours)
|
||||
assert np.array_equal(
|
||||
ems.ac_charge_hours, ac_charge_hours
|
||||
), "AC charge hours should be set correctly"
|
||||
|
||||
|
||||
def test_set_akku_dc_charge_hours(create_ems_instance):
|
||||
"""Test the set_akku_dc_charge_hours method of EnergieManagementSystem."""
|
||||
ems = create_ems_instance
|
||||
dc_charge_hours = np.full(ems.config.prediction_hours, 1.0)
|
||||
ems.set_akku_dc_charge_hours(dc_charge_hours)
|
||||
assert np.array_equal(
|
||||
ems.dc_charge_hours, dc_charge_hours
|
||||
), "DC charge hours should be set correctly"
|
||||
|
||||
|
||||
def test_set_ev_charge_hours(create_ems_instance):
|
||||
"""Test the set_ev_charge_hours method of EnergieManagementSystem."""
|
||||
ems = create_ems_instance
|
||||
ev_charge_hours = np.full(ems.config.prediction_hours, 1.0)
|
||||
ems.set_ev_charge_hours(ev_charge_hours)
|
||||
assert np.array_equal(
|
||||
ems.ev_charge_hours, ev_charge_hours
|
||||
), "EV charge hours should be set correctly"
|
||||
|
||||
|
||||
def test_reset(create_ems_instance):
|
||||
"""Test the reset method of EnergieManagementSystem."""
|
||||
ems = create_ems_instance
|
||||
ems.reset()
|
||||
assert ems.ev.current_soc_percentage() == 100, "EV SOC should be reset to initial value"
|
||||
assert (
|
||||
ems.battery.current_soc_percentage() == 80
|
||||
), "Battery SOC should be reset to initial value"
|
||||
|
||||
|
||||
def test_simulate_start_now(create_ems_instance):
|
||||
"""Test the simulate_start_now method of EnergieManagementSystem."""
|
||||
ems = create_ems_instance
|
||||
result = ems.simulate_start_now()
|
||||
assert result is not None, "Result should not be None"
|
||||
assert isinstance(result, dict), "Result should be a dictionary"
|
||||
assert "Last_Wh_pro_Stunde" in result, "Result should contain 'Last_Wh_pro_Stunde'"
|
||||
|
9
tests/testdata/optimize_input_1.json
vendored
9
tests/testdata/optimize_input_1.json
vendored
@@ -1,7 +1,14 @@
|
||||
{
|
||||
"ems": {
|
||||
"preis_euro_pro_wh_akku": 0.0001,
|
||||
"einspeiseverguetung_euro_pro_wh": 0.00007,
|
||||
"einspeiseverguetung_euro_pro_wh": [
|
||||
0.00007, 0.00007, 0.00007, 0.00007, 0.00007, 0.00007, 0.00007, 0.00007, 0.00007,
|
||||
0.00007, 0.00007, 0.00007, 0.00007, 0.00007, 0.00007, 0.00007, 0.00007, 0.00007,
|
||||
0.00007, 0.00007, 0.00007, 0.00007, 0.00007, 0.00007, 0.00007, 0.00007, 0.00007,
|
||||
0.00007, 0.00007, 0.00007, 0.00007, 0.00007, 0.00007, 0.00007, 0.00007, 0.00007,
|
||||
0.00007, 0.00007, 0.00007, 0.00007, 0.00007, 0.00007, 0.00007, 0.00007, 0.00007,
|
||||
0.00007, 0.00007, 0.00007
|
||||
],
|
||||
"gesamtlast": [
|
||||
676.71, 876.19, 527.13, 468.88, 531.38, 517.95, 483.15, 472.28, 1011.68, 995.00,
|
||||
1053.07, 1063.91, 1320.56, 1132.03, 1163.67, 1176.82, 1216.22, 1103.78, 1129.12,
|
||||
|
Reference in New Issue
Block a user