mirror of
				https://github.com/Akkudoktor-EOS/EOS.git
				synced 2025-10-30 22:36:21 +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