Update manual documentation for nested config.

* Add config_file_path, config_folder_path back to general
   (ConfigCommonSettings). Overwrite in docs generation.
This commit is contained in:
Dominique Lasserre 2025-01-19 21:47:21 +01:00
parent af5e4a753a
commit 1658b491d2
14 changed files with 282 additions and 184 deletions

View File

@ -15,6 +15,8 @@ General configuration to set directories of cache and output files.
| data_cache_subpath | `EOS_GENERAL__DATA_CACHE_SUBPATH` | `Optional[pathlib.Path]` | `rw` | `cache` | Sub-path for the EOS cache data directory. | | data_cache_subpath | `EOS_GENERAL__DATA_CACHE_SUBPATH` | `Optional[pathlib.Path]` | `rw` | `cache` | Sub-path for the EOS cache data directory. |
| data_output_path | | `Optional[pathlib.Path]` | `ro` | `N/A` | Compute data_output_path based on data_folder_path. | | data_output_path | | `Optional[pathlib.Path]` | `ro` | `N/A` | Compute data_output_path based on data_folder_path. |
| data_cache_path | | `Optional[pathlib.Path]` | `ro` | `N/A` | Compute data_cache_path based on data_folder_path. | | data_cache_path | | `Optional[pathlib.Path]` | `ro` | `N/A` | Compute data_cache_path based on data_folder_path. |
| config_folder_path | | `Optional[pathlib.Path]` | `ro` | `N/A` | Path to EOS configuration directory. |
| config_file_path | | `Optional[pathlib.Path]` | `ro` | `N/A` | Path to EOS configuration file. |
::: :::
### Example Input ### Example Input
@ -42,7 +44,9 @@ General configuration to set directories of cache and output files.
"data_output_subpath": "output", "data_output_subpath": "output",
"data_cache_subpath": "cache", "data_cache_subpath": "cache",
"data_output_path": null, "data_output_path": null,
"data_cache_path": null "data_cache_path": null,
"config_folder_path": "/home/user/.config/net.akkudoktoreos.net",
"config_file_path": "/home/user/.config/net.akkudoktoreos.net/EOS.config.json"
} }
} }
``` ```

View File

@ -192,11 +192,11 @@ Returns:
Fastapi Config Put Fastapi Config Put
``` ```
Write the provided settings into the current settings. Update the current config with the provided settings.
The existing settings are completely overwritten. Note that for any setting Note that for any setting value that is None or unset, the configuration will fall back to
value that is None, the configuration will fall back to values from other sources such as values from other sources such as environment variables, the EOS configuration file, or default
environment variables, the EOS configuration file, or default values. values.
Args: Args:
settings (SettingsEOS): The settings to write into the current settings. settings (SettingsEOS): The settings to write into the current settings.

View File

@ -7,10 +7,9 @@ management.
## Storing Configuration ## Storing Configuration
EOS stores configuration data in a **key-value store**, where a `configuration key` refers to the EOS stores configuration data in a `nested structure`. Note that configuration changes inside EOS
unique identifier used to store and retrieve specific configuration data. Note that the key-value are updated in memory, meaning all changes will be lost upon restarting the EOS REST server if not
store is memory-based, meaning all stored data will be lost upon restarting the EOS REST server if saved to the `EOS configuration file`.
not saved to the `EOS configuration file`.
Some `configuration keys` are read-only and cannot be altered. These keys are either set up by other Some `configuration keys` are read-only and cannot be altered. These keys are either set up by other
means, such as environment variables, or determined from other information. means, such as environment variables, or determined from other information.
@ -25,32 +24,33 @@ Use endpoint `PUT /v1/config/file` to save the current configuration to the
### Load Configuration File ### Load Configuration File
Use endpoint `POST /v1/config/update` to update the configuration from the `EOS configuration file`. Use endpoint `POST /v1/config/reset` to reset the configuration to the values in the
`EOS configuration file`.
## Configuration Sources and Priorities ## Configuration Sources and Priorities
The configuration sources and their priorities are as follows: The configuration sources and their priorities are as follows:
1. **Settings**: Provided during runtime by the REST interface 1. **Runtime Config Updates**: 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**
### Settings ### Runtime Config Updates
Settings are sets of configuration data that take precedence over all other configuration data from The EOS configuration can be updated at runtime. Note that those updates are not persistet
different sources. Note that settings are not persistent. To make the current configuration with the automatically. However it is possible to save the configuration to the `EOS configuration file`.
current settings persistent, save the configuration to the `EOS configuration file`.
Use the following endpoints to change the current configuration settings: Use the following endpoints to change the current runtime configuration:
- `PUT /v1/config`: Replaces the entire configuration settings. - `PUT /v1/config`: Update the entire or parts of the configuration.
- `PUT /v1/config/value`: Sets a specific configuration option.
### Environment Variables ### Environment Variables
All `configuration keys` can be set by environment variables with the same name. EOS recognizes the All `configuration keys` can be set by environment variables prefixed with `EOS_` and separated by
following special environment variables: `__` for nested structures. Environment variables are case insensitive.
EOS recognizes the following special environment variables (case sensitive):
- `EOS_CONFIG_DIR`: The directory to search for an EOS configuration file. - `EOS_CONFIG_DIR`: The directory to search for an EOS configuration file.
- `EOS_DIR`: The directory used by EOS for data, which will also be searched for an EOS - `EOS_DIR`: The directory used by EOS for data, which will also be searched for an EOS
@ -65,7 +65,7 @@ If you do not have a configuration file, it will be automatically created on the
the REST server in a system-dependent location. the REST server in a system-dependent location.
To determine the location of the configuration file used by EOS, ask the REST server. The endpoint To determine the location of the configuration file used by EOS, ask the REST server. The endpoint
`GET /v1/config` provides the `config_file_path` configuration key. `GET /v1/config` provides the `general.config_file_path` configuration key.
EOS searches for the configuration file in the following order: EOS searches for the configuration file in the following order:
@ -74,9 +74,15 @@ EOS searches for the configuration file in the following order:
3. A platform-specific default directory for EOS 3. A platform-specific default directory for EOS
4. The current working directory 4. The current working directory
The first available configuration file found in these directories is loaded. If no configuration The first configuration file available in these directories is loaded. If no configuration file is
file is found, a default configuration file is created in the platform-specific default directory, found, a default configuration file is created, and the default settings are written to it. The
and default settings are loaded into it. location of the created configuration file follows the same order in which EOS searches for
configuration files, and it depends on whether the relevant environment variables are set.
Use the following endpoints to interact with the configuration file:
- `PUT /v1/config/file`: Save the current configuration to the configuration file.
- `PUT /v1/config/reset`: Reload the configuration file, all unsaved runtime configuration is reset.
### Default Values ### Default Values

View File

@ -19,10 +19,14 @@ data is lost on re-start of the EOS REST server.
## Prediction Providers ## Prediction Providers
Most predictions can be sourced from various providers. The specific provider to use is configured Most predictions can be sourced from various providers. The specific provider to use is configured
in the EOS configuration. For example: in the EOS configuration and can be set by prediction type. For example:
```python ```python
provider = "ClearOutside" {
"weather": {
"provider": "ClearOutside"
}
}
``` ```
Some providers offer multiple prediction keys. For instance, a weather provider might provide data Some providers offer multiple prediction keys. For instance, a weather provider might provide data
@ -107,26 +111,28 @@ Prediction keys:
Configuration options: Configuration options:
- `provider`: Electricity price provider id of provider to be used. - `elecprice`: Electricity price configuration.
- `Akkudoktor`: Retrieves from Akkudoktor.net. - `provider`: Electricity price provider id of provider to be used.
- `Import`: Imports from a file or JSON string.
- `charges_kwh`: Electricity price charges (€/kWh). - `ElecPriceAkkudoktor`: Retrieves from Akkudoktor.net.
- `import_file_path`: Path to the file to import electricity price forecast data from. - `ElecPriceImport`: Imports from a file or JSON string.
- `import_json`: JSON string, dictionary of electricity price forecast value lists.
### Akkudoktor Provider - `charges_kwh`: Electricity price charges (€/kWh).
- `provider_settings.import_file_path`: Path to the file to import electricity price forecast data from.
- `provider_settings.import_json`: JSON string, dictionary of electricity price forecast value lists.
The `Akkudoktor` provider retrieves electricity prices directly from **Akkudoktor.net**, ### ElecPriceAkkudoktor Provider
The `ElecPriceAkkudoktor` provider retrieves electricity prices directly from **Akkudoktor.net**,
which supplies price data for the next 24 hours. For periods beyond 24 hours, the provider generates which supplies price data for the next 24 hours. For periods beyond 24 hours, the provider generates
prices by extrapolating historical price data combined with the most recent actual prices obtained prices by extrapolating historical price data combined with the most recent actual prices obtained
from Akkudoktor.net. Electricity price charges given in the `charges_kwh` configuration from Akkudoktor.net. Electricity price charges given in the `charges_kwh` configuration
option are added. option are added.
### Import Provider ### ElecPriceImport Provider
The `Import` provider is designed to import electricity prices from a file or a JSON The `ElecPriceImport` provider is designed to import electricity prices from a file or a JSON
string. An external entity should update the file or JSON string whenever new prediction data string. An external entity should update the file or JSON string whenever new prediction data
becomes available. becomes available.
@ -148,14 +154,16 @@ Prediction keys:
Configuration options: Configuration options:
- `provider`: Load provider id of provider to be used. - `load`: Load configuration.
- `LoadAkkudoktor`: Retrieves from local database. - `provider`: Load provider id of provider to be used.
- `LoadImport`: Imports from a file or JSON string.
- `loadakkudoktor_year_energy`: Yearly energy consumption (kWh). - `LoadAkkudoktor`: Retrieves from local database.
- `loadimport_file_path`: Path to the file to import load forecast data from. - `LoadImport`: Imports from a file or JSON string.
- `loadimport_json`: JSON string, dictionary of load forecast value lists.
- `provider_settings.loadakkudoktor_year_energy`: Yearly energy consumption (kWh).
- `provider_settings.loadimport_file_path`: Path to the file to import load forecast data from.
- `provider_settings.loadimport_json`: JSON string, dictionary of load forecast value lists.
### LoadAkkudoktor Provider ### LoadAkkudoktor Provider
@ -183,44 +191,49 @@ or `loadimport_json` configuration option.
Prediction keys: Prediction keys:
- `ac_power`: Total DC power (W). - `pvforecast_ac_power`: Total DC power (W).
- `dc_power`: Total AC power (W). - `pvforecast_dc_power`: Total AC power (W).
Configuration options: Configuration options:
- `provider`: PVForecast provider id of provider to be used. - `prediction`: General prediction configuration.
- `PVForecastAkkudoktor`: Retrieves from Akkudoktor.net. - `latitude`: Latitude in decimal degrees, between -90 and 90, north is positive (ISO 19115) (°)"
- `PVForecastImport`: Imports from a file or JSON string. - `longitude`: Longitude in decimal degrees, within -180 to 180 (°)
- `latitude`: Latitude in decimal degrees, between -90 and 90, north is positive (ISO 19115) (°)" - `pvforecast`: PV forecast configuration.
- `longitude`: Longitude in decimal degrees, within -180 to 180 (°)
- `pvforecast<0..5>_surface_tilt`: Tilt angle from horizontal plane. Ignored for two-axis tracking. - `provider`: PVForecast provider id of provider to be used.
- `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. - `PVForecastAkkudoktor`: Retrieves from Akkudoktor.net.
- `pvforecast<0..5>_peakpower`: Nominal power of PV system in kW. - `PVForecastImport`: Imports from a file or JSON string.
- `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. - `planes[].surface_tilt`: Tilt angle from horizontal plane. Ignored for two-axis tracking.
- `pvforecast<0..5>_loss`: Sum of PV system losses in percent - `planes[].surface_azimuth`: Orientation (azimuth angle) of the (fixed) plane. Clockwise from north (north=0, east=90, south=180, west=270).
- `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. - `planes[].userhorizon`: Elevation of horizon in degrees, at equally spaced azimuth clockwise from north.
- `pvforecast<0..5>_optimal_surface_tilt`: Calculate the optimum tilt angle. Ignored for two-axis tracking. - `planes[].peakpower`: Nominal power of PV system in kW.
- `pvforecast<0..5>_optimalangles`: Calculate the optimum tilt and azimuth angles. Ignored for two-axis tracking. - `planes[].pvtechchoice`: PV technology. One of 'crystSi', 'CIS', 'CdTe', 'Unknown'.
- `pvforecast<0..5>_albedo`: Proportion of the light hitting the ground that it reflects back. - `planes[].mountingplace`: Type of mounting for PV system. Options are 'free' for free-standing and 'building' for building-integrated.
- `pvforecast<0..5>_module_model`: Model of the PV modules of this plane. - `planes[].loss`: Sum of PV system losses in percent
- `pvforecast<0..5>_inverter_model`: Model of the inverter of this plane. - `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.
- `pvforecast<0..5>_inverter_paco`: AC power rating of the inverter. [W] - `planes[].optimal_surface_tilt`: Calculate the optimum tilt angle. Ignored for two-axis tracking.
- `pvforecast<0..5>_modules_per_string`: Number of the PV modules of the strings of this plane. - `planes[].optimalangles`: Calculate the optimum tilt and azimuth angles. Ignored for two-axis tracking.
- `pvforecast<0..5>_strings_per_inverter`: Number of the strings of the inverter of this plane. - `planes[].albedo`: Proportion of the light hitting the ground that it reflects back.
- `import_file_path`: Path to the file to import PV forecast data from. - `planes[].module_model`: Model of the PV modules of this plane.
- `import_json`: JSON string, dictionary of PV forecast value lists. - `planes[].inverter_model`: Model of the inverter of this plane.
- `planes[].inverter_paco`: AC power rating of the inverter. [W]
- `planes[].modules_per_string`: Number of the PV modules of the strings of this plane.
- `planes[].strings_per_inverter`: Number of the strings of the inverter of this plane.
- `provider_settings.import_file_path`: Path to the file to import PV forecast data from.
- `provider_settings.import_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 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**: Detailed definitions taken from **PVGIS**:
- `pvforecast<0..5>_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).
@ -228,19 +241,19 @@ For other technologies (especially various amorphous technologies), this correct
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` - `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` - `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` - `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).
@ -248,7 +261,7 @@ In PVGIS there are two possibilities: free-standing, meaning that the modules ar
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` - `userhorizon`
Elevation of horizon in degrees, at equally spaced azimuth clockwise from north. In the user horizon Elevation of horizon in degrees, at equally spaced azimuth clockwise from north. In the user horizon
data each number represents the horizon height in degrees in a certain compass direction around the data each number represents the horizon height in degrees in a certain compass direction around the
@ -260,15 +273,15 @@ 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 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.
Detailed definitions from **PVLib** for PVGIS data. Detailed definitions from **PVLib** for PVGIS data.
- `pvforecast<0..5>_surface_tilt`: - `surface_tilt`:
Tilt angle from horizontal plane. Tilt angle from horizontal plane.
- `pvforecast<0..5>_surface_azimuth` - `surface_azimuth`
Orientation (azimuth angle) of the (fixed) plane. Clockwise from north (north=0, east=90, south=180, 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. west=270). This is offset 180 degrees from the convention used by PVGIS.
@ -280,47 +293,60 @@ west=270). This is offset 180 degrees from the convention used by PVGIS.
The `PVForecastAkkudoktor` provider retrieves the PV power forecast data directly from The `PVForecastAkkudoktor` provider retrieves the PV power forecast data directly from
**Akkudoktor.net**. **Akkudoktor.net**.
The following general configuration options of the PV system must be set: The following prediction configuration options of the PV system must be set:
- `latitude`: Latitude in decimal degrees, between -90 and 90, north is positive (ISO 19115) (°)" - `prediction.latitude`: Latitude in decimal degrees, between -90 and 90, north is positive (ISO 19115) (°)"
- `longitude`: Longitude in decimal degrees, within -180 to 180 (°) - `prediction.longitude`: Longitude in decimal degrees, within -180 to 180 (°)
For each plane `<0..5>` 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<0..5>_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<0..5>_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<0..5>_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<0..5>_inverter_paco`: AC power rating of the inverter. [W] - `pvforecast.planes[].inverter_paco`: AC power rating of the inverter. [W]
- `pvforecast<0..5>_peakpower`: Nominal power of PV system in kW. - `pvforecast.planes[].peakpower`: Nominal power of PV system in kW.
Example: Example:
```Python ```Python
{ {
"latitude": 50.1234, "prediction": {
"longitude": 9.7654, "latitude": 50.1234,
"provider": "PVForecastAkkudoktor", "longitude": 9.7654,
"pvforecast0_peakpower": 5.0, },
"pvforecast0_surface_azimuth": -10, "pvforecast": {
"pvforecast0_surface_tilt": 7, "provider": "PVForecastAkkudoktor",
"pvforecast0_userhorizon": [20, 27, 22, 20], "planes": [
"pvforecast0_inverter_paco": 10000, {
"pvforecast1_peakpower": 4.8, "peakpower": 5.0,
"pvforecast1_surface_azimuth": -90, "surface_azimuth": -10,
"pvforecast1_surface_tilt": 7, "surface_tilt": 7,
"pvforecast1_userhorizon": [30, 30, 30, 50], "userhorizon": [20, 27, 22, 20],
"pvforecast1_inverter_paco": 10000, "inverter_paco": 10000,
"pvforecast2_peakpower": 1.4, },
"pvforecast2_surface_azimuth": -40, {
"pvforecast2_surface_tilt": 60, "peakpower": 4.8,
"pvforecast2_userhorizon": [60, 30, 0, 30], "surface_azimuth": -90,
"pvforecast2_inverter_paco": 2000, "surface_tilt": 7,
"pvforecast3_peakpower": 1.6, "userhorizon": [30, 30, 30, 50],
"pvforecast3_surface_azimuth": 5, "inverter_paco": 10000,
"pvforecast3_surface_tilt": 45, },
"pvforecast3_userhorizon": [45, 25, 30, 60], {
"pvforecast3_inverter_paco": 1400, "peakpower": 1.4,
"pvforecast4_peakpower": None, "surface_azimuth": -40,
"surface_tilt": 60,
"userhorizon": [60, 30, 0, 30],
"inverter_paco": 2000,
},
{
"peakpower": 1.6,
"surface_azimuth": 5,
"surface_tilt": 45,
"userhorizon": [45, 25, 30, 60],
"inverter_paco": 1400,
}
]
}
} }
``` ```
@ -332,8 +358,8 @@ becomes available.
The prediction keys for the PV forecast data are: The prediction keys for the PV forecast data are:
- `ac_power`: Total DC power (W). - `pvforecast_ac_power`: Total DC power (W).
- `dc_power`: Total AC power (W). - `pvforecast_dc_power`: Total AC power (W).
The PV forecast data must be provided in one of the formats described in The PV forecast data must be provided in one of the formats described in
<project:#prediction-import-providers>. The data source must be given in the <project:#prediction-import-providers>. The data source must be given in the
@ -368,14 +394,16 @@ Prediction keys:
Configuration options: Configuration options:
- `provider`: Load provider id of provider to be used. - `weather`: General weather configuration.
- `BrightSky`: Retrieves from https://api.brightsky.dev. - `provider`: Load provider id of provider to be used.
- `ClearOutside`: Retrieves from https://clearoutside.com/forecast.
- `LoadImport`: Imports from a file or JSON string.
- `import_file_path`: Path to the file to import weatherforecast data from. - `BrightSky`: Retrieves from https://api.brightsky.dev.
- `import_json`: JSON string, dictionary of weather forecast value lists. - `ClearOutside`: Retrieves from https://clearoutside.com/forecast.
- `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_json`: JSON string, dictionary of weather forecast value lists.
### BrightSky Provider ### BrightSky Provider

View File

@ -161,6 +161,34 @@
"ConfigCommonSettings-Output": { "ConfigCommonSettings-Output": {
"description": "Settings for common configuration.\n\nGeneral configuration to set directories of cache and output files.", "description": "Settings for common configuration.\n\nGeneral configuration to set directories of cache and output files.",
"properties": { "properties": {
"config_file_path": {
"anyOf": [
{
"format": "path",
"type": "string"
},
{
"type": "null"
}
],
"description": "Path to EOS configuration file.",
"readOnly": true,
"title": "Config File Path"
},
"config_folder_path": {
"anyOf": [
{
"format": "path",
"type": "string"
},
{
"type": "null"
}
],
"description": "Path to EOS configuration directory.",
"readOnly": true,
"title": "Config Folder Path"
},
"data_cache_path": { "data_cache_path": {
"anyOf": [ "anyOf": [
{ {
@ -237,7 +265,9 @@
}, },
"required": [ "required": [
"data_output_path", "data_output_path",
"data_cache_path" "data_cache_path",
"config_folder_path",
"config_file_path"
], ],
"title": "ConfigCommonSettings", "title": "ConfigCommonSettings",
"type": "object" "type": "object"
@ -257,6 +287,8 @@
"general": { "general": {
"$ref": "#/components/schemas/ConfigCommonSettings-Output", "$ref": "#/components/schemas/ConfigCommonSettings-Output",
"default": { "default": {
"config_file_path": "/home/user/.config/net.akkudoktoreos.net/EOS.config.json",
"config_folder_path": "/home/user/.config/net.akkudoktoreos.net",
"data_cache_subpath": "cache", "data_cache_subpath": "cache",
"data_output_subpath": "output" "data_output_subpath": "output"
} }
@ -3087,7 +3119,7 @@
] ]
}, },
"put": { "put": {
"description": "Write the provided settings into the current settings.\n\nThe existing settings are completely overwritten. Note that for any setting\nvalue that is None, the configuration will fall back to values from other sources such as\nenvironment variables, the EOS configuration file, or default values.\n\nArgs:\n settings (SettingsEOS): The settings to write into the current settings.\n\nReturns:\n configuration (ConfigEOS): The current configuration after the write.", "description": "Update the current config with the provided settings.\n\nNote that for any setting value that is None or unset, the configuration will fall back to\nvalues from other sources such as environment variables, the EOS configuration file, or default\nvalues.\n\nArgs:\n settings (SettingsEOS): The settings to write into the current settings.\n\nReturns:\n configuration (ConfigEOS): The current configuration after the write.",
"operationId": "fastapi_config_put_v1_config_put", "operationId": "fastapi_config_put_v1_config_put",
"requestBody": { "requestBody": {
"content": { "content": {

View File

@ -5,20 +5,19 @@ import argparse
import json import json
import sys import sys
import textwrap import textwrap
from pathlib import Path
from typing import Any, Union from typing import Any, Union
from pydantic.fields import ComputedFieldInfo, FieldInfo from pydantic.fields import ComputedFieldInfo, FieldInfo
from pydantic_core import PydanticUndefined from pydantic_core import PydanticUndefined
from akkudoktoreos.config.config import get_config from akkudoktoreos.config.config import ConfigCommonSettings, ConfigEOS, get_config
from akkudoktoreos.core.logging import get_logger from akkudoktoreos.core.logging import get_logger
from akkudoktoreos.core.pydantic import PydanticBaseModel from akkudoktoreos.core.pydantic import PydanticBaseModel
from akkudoktoreos.utils.docs import get_model_structure_from_examples from akkudoktoreos.utils.docs import get_model_structure_from_examples
logger = get_logger(__name__) logger = get_logger(__name__)
config_eos = get_config()
documented_types: set[PydanticBaseModel] = set() documented_types: set[PydanticBaseModel] = set()
undocumented_types: dict[PydanticBaseModel, tuple[str, list[str]]] = dict() undocumented_types: dict[PydanticBaseModel, tuple[str, list[str]]] = dict()
@ -238,12 +237,18 @@ def generate_config_table_md(
return table return table
def generate_config_md() -> str: def generate_config_md(config_eos: ConfigEOS) -> str:
"""Generate configuration specification in Markdown with extra tables for prefixed values. """Generate configuration specification in Markdown with extra tables for prefixed values.
Returns: Returns:
str: The Markdown representation of the configuration spec. str: The Markdown representation of the configuration spec.
""" """
# Fix file path for general settings to not show local/test file path
ConfigCommonSettings._config_file_path = Path(
"/home/user/.config/net.akkudoktoreos.net/EOS.config.json"
)
ConfigCommonSettings._config_folder_path = config_eos.general.config_file_path.parent
markdown = "# Configuration Table\n\n" markdown = "# Configuration Table\n\n"
# Generate tables for each top level config # Generate tables for each top level config
@ -271,9 +276,10 @@ def main():
) )
args = parser.parse_args() args = parser.parse_args()
config_eos = get_config()
try: try:
config_md = generate_config_md() config_md = generate_config_md(config_eos)
if args.output_file: if args.output_file:
# Write to file # Write to file
with open(args.output_file, "w", encoding="utf8") as f: with open(args.output_file, "w", encoding="utf8") as f:

View File

@ -37,6 +37,11 @@ def generate_openapi() -> dict:
routes=app.routes, routes=app.routes,
) )
# Fix file path for general settings to not show local/test file path
general = openapi_spec["components"]["schemas"]["ConfigEOS"]["properties"]["general"]["default"]
general["config_file_path"] = "/home/user/.config/net.akkudoktoreos.net/EOS.config.json"
general["config_folder_path"] = "/home/user/.config/net.akkudoktoreos.net"
return openapi_spec return openapi_spec

View File

@ -39,27 +39,36 @@ def prepare_optimization_real_parameters() -> OptimizationParameters:
# PV Forecast # PV Forecast
"pvforecast": { "pvforecast": {
"provider": "PVForecastAkkudoktor", "provider": "PVForecastAkkudoktor",
"pvforecast0_peakpower": 5.0, "planes": [
"pvforecast0_surface_azimuth": -10, {
"pvforecast0_surface_tilt": 7, "peakpower": 5.0,
"pvforecast0_userhorizon": [20, 27, 22, 20], "surface_azimuth": -10,
"pvforecast0_inverter_paco": 10000, "surface_tilt": 7,
"pvforecast1_peakpower": 4.8, "userhorizon": [20, 27, 22, 20],
"pvforecast1_surface_azimuth": -90, "inverter_paco": 10000,
"pvforecast1_surface_tilt": 7, },
"pvforecast1_userhorizon": [30, 30, 30, 50], {
"pvforecast1_inverter_paco": 10000, "peakpower": 4.8,
"pvforecast2_peakpower": 1.4, "surface_azimuth": -90,
"pvforecast2_surface_azimuth": -40, "surface_tilt": 7,
"pvforecast2_surface_tilt": 60, "userhorizon": [30, 30, 30, 50],
"pvforecast2_userhorizon": [60, 30, 0, 30], "inverter_paco": 10000,
"pvforecast2_inverter_paco": 2000, },
"pvforecast3_peakpower": 1.6, {
"pvforecast3_surface_azimuth": 5, "peakpower": 1.4,
"pvforecast3_surface_tilt": 45, "surface_azimuth": -40,
"pvforecast3_userhorizon": [45, 25, 30, 60], "surface_tilt": 60,
"pvforecast3_inverter_paco": 1400, "userhorizon": [60, 30, 0, 30],
"pvforecast4_peakpower": None, "inverter_paco": 2000,
},
{
"peakpower": 1.6,
"surface_azimuth": 5,
"surface_tilt": 45,
"userhorizon": [45, 25, 30, 60],
"inverter_paco": 1400,
},
],
}, },
# Weather Forecast # Weather Forecast
"weather": { "weather": {
@ -67,7 +76,7 @@ def prepare_optimization_real_parameters() -> OptimizationParameters:
}, },
# Electricity Price Forecast # Electricity Price Forecast
"elecprice": { "elecprice": {
"provider": "Akkudoktor", "provider": "ElecPriceAkkudoktor",
}, },
# Load Forecast # Load Forecast
"load": { "load": {
@ -298,7 +307,7 @@ def prepare_optimization_parameters() -> OptimizationParameters:
"initial_soc_percentage": 15, "initial_soc_percentage": 15,
"min_soc_percentage": 15, "min_soc_percentage": 15,
}, },
"inverter": {"device_id": "iv1", "max_power_wh": 10000, "battery": "battery1"}, "inverter": {"device_id": "iv1", "max_power_wh": 10000, "battery_id": "battery1"},
"eauto": { "eauto": {
"device_id": "ev1", "device_id": "ev1",
"min_soc_percentage": 50, "min_soc_percentage": 50,

View File

@ -68,6 +68,9 @@ class ConfigCommonSettings(SettingsBaseModel):
General configuration to set directories of cache and output files. General configuration to set directories of cache and output files.
""" """
_config_folder_path: ClassVar[Optional[Path]] = None
_config_file_path: ClassVar[Optional[Path]] = None
data_folder_path: Optional[Path] = Field( data_folder_path: Optional[Path] = Field(
default=None, description="Path to EOS data directory.", examples=[None, "/home/eos/data"] default=None, description="Path to EOS data directory.", examples=[None, "/home/eos/data"]
) )
@ -87,13 +90,24 @@ class ConfigCommonSettings(SettingsBaseModel):
"""Compute data_output_path based on data_folder_path.""" """Compute data_output_path based on data_folder_path."""
return get_absolute_path(self.data_folder_path, self.data_output_subpath) return get_absolute_path(self.data_folder_path, self.data_output_subpath)
# Computed fields
@computed_field # type: ignore[prop-decorator] @computed_field # type: ignore[prop-decorator]
@property @property
def data_cache_path(self) -> Optional[Path]: def data_cache_path(self) -> Optional[Path]:
"""Compute data_cache_path based on data_folder_path.""" """Compute data_cache_path based on data_folder_path."""
return get_absolute_path(self.data_folder_path, self.data_cache_subpath) return get_absolute_path(self.data_folder_path, self.data_cache_subpath)
@computed_field # type: ignore[prop-decorator]
@property
def config_folder_path(self) -> Optional[Path]:
"""Path to EOS configuration directory."""
return self._config_folder_path
@computed_field # type: ignore[prop-decorator]
@property
def config_file_path(self) -> Optional[Path]:
"""Path to EOS configuration file."""
return self._config_file_path
class SettingsEOS(BaseSettings): class SettingsEOS(BaseSettings):
"""Settings for all EOS. """Settings for all EOS.
@ -191,9 +205,6 @@ class ConfigEOS(SingletonMixin, SettingsEOSDefaults):
ENCODING: ClassVar[str] = "UTF-8" ENCODING: ClassVar[str] = "UTF-8"
CONFIG_FILE_NAME: ClassVar[str] = "EOS.config.json" CONFIG_FILE_NAME: ClassVar[str] = "EOS.config.json"
_config_folder_path: ClassVar[Optional[Path]] = None
_config_file_path: ClassVar[Optional[Path]] = None
@classmethod @classmethod
def settings_customise_sources( def settings_customise_sources(
cls, cls,
@ -224,7 +235,8 @@ class ConfigEOS(SingletonMixin, SettingsEOSDefaults):
2. If the configuration file does not exist, creates the directory (if needed) and attempts to copy a 2. If the configuration file does not exist, creates the directory (if needed) and attempts to copy a
default configuration file to the location. If the copy fails, uses the default configuration file directly. default configuration file to the location. If the copy fails, uses the default configuration file directly.
3. Creates a `JsonConfigSettingsSource` for both the configuration file and the default configuration file. 3. Creates a `JsonConfigSettingsSource` for both the configuration file and the default configuration file.
4. Updates class attributes `_config_folder_path` and `_config_file_path` to reflect the determined paths. 4. Updates class attributes `ConfigCommonSettings._config_folder_path` and
`ConfigCommonSettings._config_file_path` to reflect the determined paths.
5. Returns a tuple containing all provided and newly created settings sources in the desired order. 5. Returns a tuple containing all provided and newly created settings sources in the desired order.
Notes: Notes:
@ -246,8 +258,8 @@ class ConfigEOS(SingletonMixin, SettingsEOSDefaults):
default_settings = JsonConfigSettingsSource( default_settings = JsonConfigSettingsSource(
settings_cls, json_file=cls.config_default_file_path settings_cls, json_file=cls.config_default_file_path
) )
cls._config_folder_path = config_dir ConfigCommonSettings._config_folder_path = config_dir
cls._config_file_path = config_file ConfigCommonSettings._config_file_path = config_file
return ( return (
init_settings, init_settings,
@ -258,16 +270,6 @@ class ConfigEOS(SingletonMixin, SettingsEOSDefaults):
default_settings, default_settings,
) )
@property
def config_folder_path(self) -> Optional[Path]:
"""Path to EOS configuration directory."""
return self._config_folder_path
@property
def config_file_path(self) -> Optional[Path]:
"""Path to EOS configuration file."""
return self._config_file_path
@classmethod @classmethod
@classproperty @classproperty
def config_default_file_path(cls) -> Path: def config_default_file_path(cls) -> Path:
@ -341,13 +343,15 @@ class ConfigEOS(SingletonMixin, SettingsEOSDefaults):
self._setup() self._setup()
def _create_initial_config_file(self) -> None: def _create_initial_config_file(self) -> None:
if self.config_file_path and not self.config_file_path.exists(): if self.general.config_file_path and not self.general.config_file_path.exists():
self.config_file_path.parent.mkdir(parents=True, exist_ok=True) self.general.config_file_path.parent.mkdir(parents=True, exist_ok=True)
try: try:
with open(self.config_file_path, "w") as f: with open(self.general.config_file_path, "w") as f:
f.write(self.model_dump_json(indent=4)) f.write(self.model_dump_json(indent=4))
except Exception as e: except Exception as e:
logger.error(f"Could not write configuration file '{self.config_file_path}': {e}") logger.error(
f"Could not write configuration file '{self.general.config_file_path}': {e}"
)
def _update_data_folder_path(self) -> None: def _update_data_folder_path(self) -> None:
"""Updates path to the data directory.""" """Updates path to the data directory."""
@ -412,9 +416,9 @@ class ConfigEOS(SingletonMixin, SettingsEOSDefaults):
Raises: Raises:
ValueError: If the configuration file path is not specified or can not be written to. ValueError: If the configuration file path is not specified or can not be written to.
""" """
if not self.config_file_path: if not self.general.config_file_path:
raise ValueError("Configuration file path unknown.") raise ValueError("Configuration file path unknown.")
with self.config_file_path.open("w", encoding=self.ENCODING) as f_out: with self.general.config_file_path.open("w", encoding=self.ENCODING) as f_out:
json_str = super().model_dump_json() json_str = super().model_dump_json()
f_out.write(json_str) f_out.write(json_str)

View File

@ -283,11 +283,11 @@ def fastapi_config_get() -> ConfigEOS:
@app.put("/v1/config", tags=["config"]) @app.put("/v1/config", tags=["config"])
def fastapi_config_put(settings: SettingsEOS) -> ConfigEOS: def fastapi_config_put(settings: SettingsEOS) -> ConfigEOS:
"""Write the provided settings into the current settings. """Update the current config with the provided settings.
The existing settings are completely overwritten. Note that for any setting Note that for any setting value that is None or unset, the configuration will fall back to
value that is None, the configuration will fall back to values from other sources such as values from other sources such as environment variables, the EOS configuration file, or default
environment variables, the EOS configuration file, or default values. values.
Args: Args:
settings (SettingsEOS): The settings to write into the current settings. settings (SettingsEOS): The settings to write into the current settings.

View File

@ -145,7 +145,7 @@ def config_eos(
assert not config_file_cwd.exists() assert not config_file_cwd.exists()
config_eos = get_config() config_eos = get_config()
config_eos.reset_settings() config_eos.reset_settings()
assert config_file == config_eos.config_file_path assert config_file == config_eos.general.config_file_path
assert config_file.exists() assert config_file.exists()
assert not config_file_cwd.exists() assert not config_file_cwd.exists()
assert config_default_dirs[-1] / "data" == config_eos.general.data_folder_path assert config_default_dirs[-1] / "data" == config_eos.general.data_folder_path

View File

@ -57,7 +57,7 @@ def test_computed_paths(config_eos):
def test_singleton_behavior(config_eos, config_default_dirs): def test_singleton_behavior(config_eos, config_default_dirs):
"""Test that ConfigEOS behaves as a singleton.""" """Test that ConfigEOS behaves as a singleton."""
initial_cfg_file = config_eos.config_file_path initial_cfg_file = config_eos.general.config_file_path
with patch( with patch(
"akkudoktoreos.config.config.user_config_dir", return_value=str(config_default_dirs[0]) "akkudoktoreos.config.config.user_config_dir", return_value=str(config_default_dirs[0])
): ):
@ -65,7 +65,7 @@ def test_singleton_behavior(config_eos, config_default_dirs):
instance2 = ConfigEOS() instance2 = ConfigEOS()
assert instance1 is config_eos assert instance1 is config_eos
assert instance1 is instance2 assert instance1 is instance2
assert instance1.config_file_path == initial_cfg_file assert instance1.general.config_file_path == initial_cfg_file
def test_default_config_path(config_eos, config_default_dirs): def test_default_config_path(config_eos, config_default_dirs):
@ -86,13 +86,13 @@ def test_config_file_priority(config_default_dirs):
config_file = Path(config_default_dir_cwd) / ConfigEOS.CONFIG_FILE_NAME config_file = Path(config_default_dir_cwd) / ConfigEOS.CONFIG_FILE_NAME
config_file.write_text("{}") config_file.write_text("{}")
config_eos = get_config() config_eos = get_config()
assert config_eos.config_file_path == config_file assert config_eos.general.config_file_path == config_file
config_file = Path(config_default_dir_user) / ConfigEOS.CONFIG_FILE_NAME config_file = Path(config_default_dir_user) / ConfigEOS.CONFIG_FILE_NAME
config_file.parent.mkdir() config_file.parent.mkdir()
config_file.write_text("{}") config_file.write_text("{}")
config_eos.update() config_eos.update()
assert config_eos.config_file_path == config_file assert config_eos.general.config_file_path == config_file
@patch("akkudoktoreos.config.config.user_config_dir") @patch("akkudoktoreos.config.config.user_config_dir")

View File

@ -86,7 +86,7 @@ def test_config_md_current(config_eos):
sys.path.insert(0, str(root_dir)) sys.path.insert(0, str(root_dir))
from scripts import generate_config_md from scripts import generate_config_md
config_md = generate_config_md.generate_config_md() config_md = generate_config_md.generate_config_md(config_eos)
with open(new_config_md_path, "w", encoding="utf8") as f_new: with open(new_config_md_path, "w", encoding="utf8") as f_new:
f_new.write(config_md) f_new.write(config_md)

View File

@ -163,10 +163,14 @@ sample_config_data = {
}, },
"pvforecast": { "pvforecast": {
"provider": "PVForecastAkkudoktor", "provider": "PVForecastAkkudoktor",
"pvforecast0_peakpower": 5.0, "planes": [
"pvforecast0_surface_azimuth": 180, {
"pvforecast0_surface_tilt": 30, "peakpower": 5.0,
"pvforecast0_inverter_paco": 10000, "surface_azimuth": 180,
"surface_tilt": 30,
"inverter_paco": 10000,
}
],
}, },
} }