mirror of
https://github.com/Akkudoktor-EOS/EOS.git
synced 2025-08-25 15:01:14 +00:00
Review comments
This commit is contained in:
@@ -27,6 +27,7 @@ from pydantic_settings.sources import ConfigFileSourceMixin
|
||||
# settings
|
||||
from akkudoktoreos.config.configabc import SettingsBaseModel
|
||||
from akkudoktoreos.core.coreabc import SingletonMixin
|
||||
from akkudoktoreos.core.decorators import classproperty
|
||||
from akkudoktoreos.core.logging import get_logger
|
||||
from akkudoktoreos.core.logsettings import LoggingCommonSettings
|
||||
from akkudoktoreos.core.pydantic import merge_models
|
||||
@@ -40,7 +41,7 @@ from akkudoktoreos.prediction.pvforecast import PVForecastCommonSettings
|
||||
from akkudoktoreos.prediction.weather import WeatherCommonSettings
|
||||
from akkudoktoreos.server.server import ServerCommonSettings
|
||||
from akkudoktoreos.utils.datetimeutil import to_timezone
|
||||
from akkudoktoreos.utils.utils import UtilsCommonSettings, classproperty
|
||||
from akkudoktoreos.utils.utils import UtilsCommonSettings
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
@@ -63,7 +64,7 @@ def get_absolute_path(
|
||||
return None
|
||||
|
||||
|
||||
class ConfigCommonSettings(SettingsBaseModel):
|
||||
class GeneralSettings(SettingsBaseModel):
|
||||
"""Settings for common configuration.
|
||||
|
||||
General configuration to set directories of cache and output files and system location (latitude
|
||||
@@ -152,7 +153,7 @@ class SettingsEOS(BaseSettings):
|
||||
Used by updating the configuration with specific settings only.
|
||||
"""
|
||||
|
||||
general: Optional[ConfigCommonSettings] = None
|
||||
general: Optional[GeneralSettings] = None
|
||||
logging: Optional[LoggingCommonSettings] = None
|
||||
devices: Optional[DevicesCommonSettings] = None
|
||||
measurement: Optional[MeasurementCommonSettings] = None
|
||||
@@ -176,7 +177,7 @@ class SettingsEOSDefaults(SettingsEOS):
|
||||
Used by ConfigEOS instance to make all fields available.
|
||||
"""
|
||||
|
||||
general: ConfigCommonSettings = ConfigCommonSettings()
|
||||
general: GeneralSettings = GeneralSettings()
|
||||
logging: LoggingCommonSettings = LoggingCommonSettings()
|
||||
devices: DevicesCommonSettings = DevicesCommonSettings()
|
||||
measurement: MeasurementCommonSettings = MeasurementCommonSettings()
|
||||
@@ -254,7 +255,7 @@ class ConfigEOS(SingletonMixin, SettingsEOSDefaults):
|
||||
"""Customizes the order and handling of settings sources for a Pydantic BaseSettings subclass.
|
||||
|
||||
This method determines the sources for application configuration settings, including
|
||||
environment variables, dotenv files, JSON configuration files, and file secrets.
|
||||
environment variables, dotenv files and JSON configuration files.
|
||||
It ensures that a default configuration file exists and creates one if necessary.
|
||||
|
||||
Args:
|
||||
@@ -262,7 +263,7 @@ class ConfigEOS(SingletonMixin, SettingsEOSDefaults):
|
||||
init_settings (PydanticBaseSettingsSource): The initial settings source, typically passed at runtime.
|
||||
env_settings (PydanticBaseSettingsSource): Settings sourced from environment variables.
|
||||
dotenv_settings (PydanticBaseSettingsSource): Settings sourced from a dotenv file.
|
||||
file_secret_settings (PydanticBaseSettingsSource): Settings sourced from secret files.
|
||||
file_secret_settings (PydanticBaseSettingsSource): Unused (needed for parent class interface).
|
||||
|
||||
Returns:
|
||||
tuple[PydanticBaseSettingsSource, ...]: A tuple of settings sources in the order they should be applied.
|
||||
@@ -272,8 +273,8 @@ class ConfigEOS(SingletonMixin, SettingsEOSDefaults):
|
||||
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.
|
||||
3. Creates a `JsonConfigSettingsSource` for both the configuration file and the default configuration file.
|
||||
4. Updates class attributes `ConfigCommonSettings._config_folder_path` and
|
||||
`ConfigCommonSettings._config_file_path` to reflect the determined paths.
|
||||
4. Updates class attributes `GeneralSettings._config_folder_path` and
|
||||
`GeneralSettings._config_file_path` to reflect the determined paths.
|
||||
5. Returns a tuple containing all provided and newly created settings sources in the desired order.
|
||||
|
||||
Notes:
|
||||
@@ -295,15 +296,14 @@ class ConfigEOS(SingletonMixin, SettingsEOSDefaults):
|
||||
default_settings = JsonConfigSettingsSource(
|
||||
settings_cls, json_file=cls.config_default_file_path
|
||||
)
|
||||
ConfigCommonSettings._config_folder_path = config_dir
|
||||
ConfigCommonSettings._config_file_path = config_file
|
||||
GeneralSettings._config_folder_path = config_dir
|
||||
GeneralSettings._config_file_path = config_file
|
||||
|
||||
return (
|
||||
init_settings,
|
||||
env_settings,
|
||||
dotenv_settings,
|
||||
file_settings,
|
||||
file_secret_settings,
|
||||
default_settings,
|
||||
)
|
||||
|
||||
|
42
src/akkudoktoreos/core/decorators.py
Normal file
42
src/akkudoktoreos/core/decorators.py
Normal file
@@ -0,0 +1,42 @@
|
||||
from typing import Any, Optional
|
||||
|
||||
from akkudoktoreos.core.logging import get_logger
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
|
||||
class classproperty(property):
|
||||
"""A decorator to define a read-only property at the class level.
|
||||
|
||||
This class extends the built-in `property` to allow a method to be accessed
|
||||
as a property on the class itself, rather than an instance. This is useful
|
||||
when you want a property-like syntax for methods that depend on the class
|
||||
rather than any instance of the class.
|
||||
|
||||
Example:
|
||||
class MyClass:
|
||||
_value = 42
|
||||
|
||||
@classproperty
|
||||
def value(cls):
|
||||
return cls._value
|
||||
|
||||
print(MyClass.value) # Outputs: 42
|
||||
|
||||
Methods:
|
||||
__get__: Retrieves the value of the class property by calling the
|
||||
decorated method on the class.
|
||||
|
||||
Parameters:
|
||||
fget (Callable[[type], Any]): A method that takes the class as an
|
||||
argument and returns a value.
|
||||
|
||||
Raises:
|
||||
AssertionError: If `fget` is not defined when `__get__` is called.
|
||||
"""
|
||||
|
||||
def __get__(self, _: Any, owner_cls: Optional[type[Any]] = None) -> Any:
|
||||
if owner_cls is None:
|
||||
return self
|
||||
assert self.fget is not None
|
||||
return self.fget(owner_cls)
|
@@ -39,153 +39,6 @@ class Devices(SingletonMixin, DevicesBase):
|
||||
device.post_setup()
|
||||
|
||||
|
||||
# # Devices
|
||||
# # TODO: Make devices class a container of device simulation providers.
|
||||
# # Device simulations to be used are then enabled in the configuration.
|
||||
# battery: ClassVar[Battery] = Battery(provider_id="GenericBattery")
|
||||
# ev: ClassVar[Battery] = Battery(provider_id="GenericBEV")
|
||||
# home_appliance: ClassVar[HomeAppliance] = HomeAppliance(provider_id="GenericDishWasher")
|
||||
# inverter: ClassVar[Inverter] = Inverter(
|
||||
# self_consumption_predictor=SelfConsumptionProbabilityInterpolator,
|
||||
# battery=battery,
|
||||
# provider_id="GenericInverter",
|
||||
# )
|
||||
#
|
||||
# def update_data(self) -> None:
|
||||
# """Update device simulation data."""
|
||||
# # Assure devices are set up
|
||||
# self.battery.setup()
|
||||
# self.ev.setup()
|
||||
# self.home_appliance.setup()
|
||||
# self.inverter.setup()
|
||||
#
|
||||
# # Pre-allocate arrays for the results, optimized for speed
|
||||
# self.last_wh_pro_stunde = np.full((self.total_hours), np.nan)
|
||||
# self.grid_export_wh_pro_stunde = np.full((self.total_hours), np.nan)
|
||||
# self.grid_import_wh_pro_stunde = np.full((self.total_hours), np.nan)
|
||||
# self.kosten_euro_pro_stunde = np.full((self.total_hours), np.nan)
|
||||
# self.einnahmen_euro_pro_stunde = np.full((self.total_hours), np.nan)
|
||||
# self.akku_soc_pro_stunde = np.full((self.total_hours), np.nan)
|
||||
# self.eauto_soc_pro_stunde = np.full((self.total_hours), np.nan)
|
||||
# self.verluste_wh_pro_stunde = np.full((self.total_hours), np.nan)
|
||||
# self.home_appliance_wh_per_hour = np.full((self.total_hours), np.nan)
|
||||
#
|
||||
# # Set initial state
|
||||
# simulation_step = to_duration("1 hour")
|
||||
# if self.battery:
|
||||
# self.akku_soc_pro_stunde[0] = self.battery.current_soc_percentage()
|
||||
# if self.ev:
|
||||
# self.eauto_soc_pro_stunde[0] = self.ev.current_soc_percentage()
|
||||
#
|
||||
# # Get predictions for full device simulation time range
|
||||
# # gesamtlast[stunde]
|
||||
# load_total_mean = self.prediction.key_to_array(
|
||||
# "load_total_mean",
|
||||
# start_datetime=self.start_datetime,
|
||||
# end_datetime=self.end_datetime,
|
||||
# interval=simulation_step,
|
||||
# )
|
||||
# # pv_prognose_wh[stunde]
|
||||
# pvforecast_ac_power = self.prediction.key_to_array(
|
||||
# "pvforecast_ac_power",
|
||||
# start_datetime=self.start_datetime,
|
||||
# end_datetime=self.end_datetime,
|
||||
# interval=simulation_step,
|
||||
# )
|
||||
# # strompreis_euro_pro_wh[stunde]
|
||||
# elecprice_marketprice_wh = self.prediction.key_to_array(
|
||||
# "elecprice_marketprice_wh",
|
||||
# start_datetime=self.start_datetime,
|
||||
# end_datetime=self.end_datetime,
|
||||
# interval=simulation_step,
|
||||
# )
|
||||
# # einspeiseverguetung_euro_pro_wh_arr[stunde]
|
||||
# # TODO: Create prediction for einspeiseverguetung_euro_pro_wh_arr
|
||||
# einspeiseverguetung_euro_pro_wh_arr = np.full((self.total_hours), 0.078)
|
||||
#
|
||||
# for stunde_since_now in range(0, self.total_hours):
|
||||
# hour = self.start_datetime.hour + stunde_since_now
|
||||
#
|
||||
# # Accumulate loads and PV generation
|
||||
# consumption = load_total_mean[stunde_since_now]
|
||||
# self.verluste_wh_pro_stunde[stunde_since_now] = 0.0
|
||||
#
|
||||
# # Home appliances
|
||||
# if self.home_appliance:
|
||||
# ha_load = self.home_appliance.get_load_for_hour(hour)
|
||||
# consumption += ha_load
|
||||
# self.home_appliance_wh_per_hour[stunde_since_now] = ha_load
|
||||
#
|
||||
# # E-Auto handling
|
||||
# if self.ev:
|
||||
# if self.ev_charge_hours[hour] > 0:
|
||||
# geladene_menge_eauto, verluste_eauto = self.ev.charge_energy(
|
||||
# None, hour, relative_power=self.ev_charge_hours[hour]
|
||||
# )
|
||||
# consumption += geladene_menge_eauto
|
||||
# self.verluste_wh_pro_stunde[stunde_since_now] += verluste_eauto
|
||||
# self.eauto_soc_pro_stunde[stunde_since_now] = self.ev.current_soc_percentage()
|
||||
#
|
||||
# # Process inverter logic
|
||||
# grid_export, grid_import, losses, self_consumption = (0.0, 0.0, 0.0, 0.0)
|
||||
# if self.battery:
|
||||
# self.battery.set_charge_allowed_for_hour(self.dc_charge_hours[hour], hour)
|
||||
# if self.inverter:
|
||||
# generation = pvforecast_ac_power[hour]
|
||||
# grid_export, grid_import, losses, self_consumption = self.inverter.process_energy(
|
||||
# generation, 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)
|
||||
# geladene_menge, verluste_wh = self.battery.charge_energy(
|
||||
# None, hour, relative_power=self.ac_charge_hours[hour]
|
||||
# )
|
||||
# # print(stunde, " ", geladene_menge, " ",self.ac_charge_hours[stunde]," ",self.battery.current_soc_percentage())
|
||||
# consumption += geladene_menge
|
||||
# grid_import += geladene_menge
|
||||
# self.verluste_wh_pro_stunde[stunde_since_now] += verluste_wh
|
||||
#
|
||||
# self.grid_export_wh_pro_stunde[stunde_since_now] = grid_export
|
||||
# self.grid_import_wh_pro_stunde[stunde_since_now] = grid_import
|
||||
# self.verluste_wh_pro_stunde[stunde_since_now] += losses
|
||||
# self.last_wh_pro_stunde[stunde_since_now] = consumption
|
||||
#
|
||||
# # Financial calculations
|
||||
# self.kosten_euro_pro_stunde[stunde_since_now] = (
|
||||
# grid_import * self.strompreis_euro_pro_wh[hour]
|
||||
# )
|
||||
# self.einnahmen_euro_pro_stunde[stunde_since_now] = (
|
||||
# grid_export * self.einspeiseverguetung_euro_pro_wh_arr[hour]
|
||||
# )
|
||||
#
|
||||
# # battery SOC tracking
|
||||
# if self.battery:
|
||||
# self.akku_soc_pro_stunde[stunde_since_now] = self.battery.current_soc_percentage()
|
||||
# else:
|
||||
# self.akku_soc_pro_stunde[stunde_since_now] = 0.0
|
||||
#
|
||||
# def report_dict(self) -> Dict[str, Any]:
|
||||
# """Provides devices simulation output as a dictionary."""
|
||||
# out: Dict[str, Optional[Union[np.ndarray, float]]] = {
|
||||
# "Last_Wh_pro_Stunde": self.last_wh_pro_stunde,
|
||||
# "grid_export_Wh_pro_Stunde": self.grid_export_wh_pro_stunde,
|
||||
# "grid_import_Wh_pro_Stunde": self.grid_import_wh_pro_stunde,
|
||||
# "Kosten_Euro_pro_Stunde": self.kosten_euro_pro_stunde,
|
||||
# "akku_soc_pro_stunde": self.akku_soc_pro_stunde,
|
||||
# "Einnahmen_Euro_pro_Stunde": self.einnahmen_euro_pro_stunde,
|
||||
# "Gesamtbilanz_Euro": self.total_balance_euro,
|
||||
# "EAuto_SoC_pro_Stunde": self.eauto_soc_pro_stunde,
|
||||
# "Gesamteinnahmen_Euro": self.total_revenues_euro,
|
||||
# "Gesamtkosten_Euro": self.total_costs_euro,
|
||||
# "Verluste_Pro_Stunde": self.verluste_wh_pro_stunde,
|
||||
# "Gesamt_Verluste": self.total_losses_wh,
|
||||
# "Home_appliance_wh_per_hour": self.home_appliance_wh_per_hour,
|
||||
# }
|
||||
# return out
|
||||
|
||||
|
||||
# Initialize the Devices simulation, it is a singleton.
|
||||
devices = Devices()
|
||||
|
||||
|
@@ -147,14 +147,12 @@ class Measurement(SingletonMixin, DataImportMixin, DataSequence):
|
||||
"""Provides measurement key for given name and topic."""
|
||||
topic = topic.lower()
|
||||
|
||||
print(self.topics)
|
||||
if topic not in self.topics:
|
||||
return None
|
||||
|
||||
topic_keys = [
|
||||
key for key in self.config.measurement.model_fields.keys() if key.startswith(topic)
|
||||
]
|
||||
print(topic_keys)
|
||||
key = None
|
||||
if topic == "load":
|
||||
for config_key in topic_keys:
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import json
|
||||
from typing import Any, Optional
|
||||
from typing import Any
|
||||
|
||||
import numpy as np
|
||||
|
||||
@@ -9,14 +9,6 @@ from akkudoktoreos.core.logging import get_logger
|
||||
logger = get_logger(__name__)
|
||||
|
||||
|
||||
class classproperty(property):
|
||||
def __get__(self, _: Any, owner_cls: Optional[type[Any]] = None) -> Any:
|
||||
if owner_cls is None:
|
||||
return self
|
||||
assert self.fget is not None
|
||||
return self.fget(owner_cls)
|
||||
|
||||
|
||||
class UtilsCommonSettings(SettingsBaseModel):
|
||||
"""Utils Configuration."""
|
||||
|
||||
|
Reference in New Issue
Block a user