mirror of
https://github.com/Akkudoktor-EOS/EOS.git
synced 2025-10-11 20:06:18 +00:00
Nested config, devices registry
* All config now nested. - Use default config from model field default values. If providers should be enabled by default, non-empty default config file could be provided again. - Environment variable support with EOS_ prefix and __ between levels, e.g. EOS_SERVER__EOS_SERVER_PORT=8503 where all values are case insensitive. For more information see: https://docs.pydantic.dev/latest/concepts/pydantic_settings/#parsing-environment-variable-values - Use devices as registry for configured devices. DeviceBase as base class with for now just initializion support (in the future expand to operations during optimization). - Strip down ConfigEOS to the only configuration instance. Reload from file or reset to defaults is possible. * Fix multi-initialization of derived SingletonMixin classes.
This commit is contained in:
@@ -1,11 +1,14 @@
|
||||
from typing import Any, Optional
|
||||
|
||||
import numpy as np
|
||||
from pydantic import BaseModel, Field, field_validator
|
||||
from pydantic import Field, field_validator
|
||||
|
||||
from akkudoktoreos.core.logging import get_logger
|
||||
from akkudoktoreos.core.pydantic import ParametersBaseModel
|
||||
from akkudoktoreos.devices.devicesabc import DeviceBase
|
||||
from akkudoktoreos.devices.devicesabc import (
|
||||
DeviceBase,
|
||||
DeviceOptimizeResult,
|
||||
DeviceParameters,
|
||||
)
|
||||
from akkudoktoreos.utils.utils import NumpyEncoder
|
||||
|
||||
logger = get_logger(__name__)
|
||||
@@ -25,9 +28,10 @@ def initial_soc_percentage_field(description: str) -> int:
|
||||
return Field(default=0, ge=0, le=100, description=description)
|
||||
|
||||
|
||||
class BaseBatteryParameters(ParametersBaseModel):
|
||||
class BaseBatteryParameters(DeviceParameters):
|
||||
"""Base class for battery parameters with fields for capacity, efficiency, and state of charge."""
|
||||
|
||||
device_id: str = Field(description="ID of battery")
|
||||
capacity_wh: int = Field(
|
||||
gt=0, description="An integer representing the capacity of the battery in watt-hours."
|
||||
)
|
||||
@@ -68,15 +72,17 @@ class SolarPanelBatteryParameters(BaseBatteryParameters):
|
||||
class ElectricVehicleParameters(BaseBatteryParameters):
|
||||
"""Parameters specific to an electric vehicle (EV)."""
|
||||
|
||||
device_id: str = Field(description="ID of electric vehicle")
|
||||
discharging_efficiency: float = 1.0
|
||||
initial_soc_percentage: int = initial_soc_percentage_field(
|
||||
"An integer representing the current state of charge (SOC) of the battery in percentage."
|
||||
)
|
||||
|
||||
|
||||
class ElectricVehicleResult(BaseModel):
|
||||
class ElectricVehicleResult(DeviceOptimizeResult):
|
||||
"""Result class containing information related to the electric vehicle's charging and discharging behavior."""
|
||||
|
||||
device_id: str = Field(description="ID of electric vehicle")
|
||||
charge_array: list[float] = Field(
|
||||
description="Hourly charging status (0 for no charging, 1 for charging)."
|
||||
)
|
||||
@@ -84,7 +90,6 @@ class ElectricVehicleResult(BaseModel):
|
||||
description="Hourly discharging status (0 for no discharging, 1 for discharging)."
|
||||
)
|
||||
discharging_efficiency: float = Field(description="The discharge efficiency as a float..")
|
||||
hours: int = Field(description="Number of hours in the simulation.")
|
||||
capacity_wh: int = Field(description="Capacity of the EV’s battery in watt-hours.")
|
||||
charging_efficiency: float = Field(description="Charging efficiency as a float..")
|
||||
max_charge_power_w: int = Field(description="Maximum charging power in watts.")
|
||||
@@ -103,81 +108,30 @@ class ElectricVehicleResult(BaseModel):
|
||||
class Battery(DeviceBase):
|
||||
"""Represents a battery device with methods to simulate energy charging and discharging."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
parameters: Optional[BaseBatteryParameters] = None,
|
||||
hours: Optional[int] = 24,
|
||||
provider_id: Optional[str] = None,
|
||||
):
|
||||
# Initialize configuration and parameters
|
||||
self.provider_id = provider_id
|
||||
self.prefix = "<invalid>"
|
||||
if self.provider_id == "GenericBattery":
|
||||
self.prefix = "battery"
|
||||
elif self.provider_id == "GenericBEV":
|
||||
self.prefix = "bev"
|
||||
def __init__(self, parameters: Optional[BaseBatteryParameters] = None):
|
||||
self.parameters: Optional[BaseBatteryParameters] = None
|
||||
super().__init__(parameters)
|
||||
|
||||
self.parameters = parameters
|
||||
if hours is None:
|
||||
self.hours = self.total_hours # TODO where does that come from?
|
||||
else:
|
||||
self.hours = hours
|
||||
|
||||
self.initialised = False
|
||||
|
||||
# Run setup if parameters are given, otherwise setup() has to be called later when the config is initialised.
|
||||
if self.parameters is not None:
|
||||
self.setup()
|
||||
|
||||
def setup(self) -> None:
|
||||
def _setup(self) -> None:
|
||||
"""Sets up the battery parameters based on configuration or provided parameters."""
|
||||
if self.initialised:
|
||||
return
|
||||
assert self.parameters is not None
|
||||
self.capacity_wh = self.parameters.capacity_wh
|
||||
self.initial_soc_percentage = self.parameters.initial_soc_percentage
|
||||
self.charging_efficiency = self.parameters.charging_efficiency
|
||||
self.discharging_efficiency = self.parameters.discharging_efficiency
|
||||
|
||||
if self.provider_id:
|
||||
# Setup from configuration
|
||||
self.capacity_wh = getattr(self.config, f"{self.prefix}_capacity")
|
||||
self.initial_soc_percentage = getattr(self.config, f"{self.prefix}_initial_soc")
|
||||
self.hours = self.total_hours # TODO where does that come from?
|
||||
self.charging_efficiency = getattr(self.config, f"{self.prefix}_charging_efficiency")
|
||||
self.discharging_efficiency = getattr(
|
||||
self.config, f"{self.prefix}_discharging_efficiency"
|
||||
)
|
||||
self.max_charge_power_w = getattr(self.config, f"{self.prefix}_max_charging_power")
|
||||
|
||||
if self.provider_id == "GenericBattery":
|
||||
self.min_soc_percentage = getattr(
|
||||
self.config,
|
||||
f"{self.prefix}_soc_min",
|
||||
)
|
||||
else:
|
||||
self.min_soc_percentage = 0
|
||||
|
||||
self.max_soc_percentage = getattr(
|
||||
self.config,
|
||||
f"{self.prefix}_soc_max",
|
||||
)
|
||||
elif self.parameters:
|
||||
# Setup from parameters
|
||||
self.capacity_wh = self.parameters.capacity_wh
|
||||
self.initial_soc_percentage = self.parameters.initial_soc_percentage
|
||||
self.charging_efficiency = self.parameters.charging_efficiency
|
||||
self.discharging_efficiency = self.parameters.discharging_efficiency
|
||||
self.max_charge_power_w = self.parameters.max_charge_power_w
|
||||
# Only assign for storage battery
|
||||
self.min_soc_percentage = (
|
||||
self.parameters.min_soc_percentage
|
||||
if isinstance(self.parameters, SolarPanelBatteryParameters)
|
||||
else 0
|
||||
)
|
||||
self.max_soc_percentage = self.parameters.max_soc_percentage
|
||||
else:
|
||||
error_msg = "Parameters and provider ID are missing. Cannot instantiate."
|
||||
logger.error(error_msg)
|
||||
raise ValueError(error_msg)
|
||||
# Only assign for storage battery
|
||||
self.min_soc_percentage = (
|
||||
self.parameters.min_soc_percentage
|
||||
if isinstance(self.parameters, SolarPanelBatteryParameters)
|
||||
else 0
|
||||
)
|
||||
self.max_soc_percentage = self.parameters.max_soc_percentage
|
||||
|
||||
# Initialize state of charge
|
||||
if self.max_charge_power_w is None:
|
||||
if self.parameters.max_charge_power_w is not None:
|
||||
self.max_charge_power_w = self.parameters.max_charge_power_w
|
||||
else:
|
||||
self.max_charge_power_w = self.capacity_wh # TODO this should not be equal capacity_wh
|
||||
self.discharge_array = np.full(self.hours, 1)
|
||||
self.charge_array = np.full(self.hours, 1)
|
||||
@@ -185,11 +139,10 @@ class Battery(DeviceBase):
|
||||
self.min_soc_wh = (self.min_soc_percentage / 100) * self.capacity_wh
|
||||
self.max_soc_wh = (self.max_soc_percentage / 100) * self.capacity_wh
|
||||
|
||||
self.initialised = True
|
||||
|
||||
def to_dict(self) -> dict[str, Any]:
|
||||
"""Converts the object to a dictionary representation."""
|
||||
return {
|
||||
"device_id": self.device_id,
|
||||
"capacity_wh": self.capacity_wh,
|
||||
"initial_soc_percentage": self.initial_soc_percentage,
|
||||
"soc_wh": self.soc_wh,
|
||||
|
@@ -1,307 +1,189 @@
|
||||
from typing import Any, ClassVar, Dict, Optional, Union
|
||||
from typing import Optional
|
||||
|
||||
import numpy as np
|
||||
from numpydantic import NDArray, Shape
|
||||
from pydantic import Field, computed_field
|
||||
|
||||
from akkudoktoreos.config.configabc import SettingsBaseModel
|
||||
from akkudoktoreos.core.coreabc import SingletonMixin
|
||||
from akkudoktoreos.core.logging import get_logger
|
||||
from akkudoktoreos.devices.battery import Battery
|
||||
from akkudoktoreos.devices.devicesabc import DevicesBase
|
||||
from akkudoktoreos.devices.generic import HomeAppliance
|
||||
from akkudoktoreos.devices.inverter import Inverter
|
||||
from akkudoktoreos.prediction.interpolator import SelfConsumptionProbabilityInterpolator
|
||||
from akkudoktoreos.utils.datetimeutil import to_duration
|
||||
from akkudoktoreos.devices.settings import DevicesCommonSettings
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
|
||||
class DevicesCommonSettings(SettingsBaseModel):
|
||||
"""Base configuration for devices simulation settings."""
|
||||
|
||||
# Battery
|
||||
# -------
|
||||
battery_provider: Optional[str] = Field(
|
||||
default=None, description="Id of Battery simulation provider."
|
||||
)
|
||||
battery_capacity: Optional[int] = Field(default=None, description="Battery capacity [Wh].")
|
||||
battery_initial_soc: Optional[int] = Field(
|
||||
default=None, description="Battery initial state of charge [%]."
|
||||
)
|
||||
battery_soc_min: Optional[int] = Field(
|
||||
default=None, description="Battery minimum state of charge [%]."
|
||||
)
|
||||
battery_soc_max: Optional[int] = Field(
|
||||
default=None, description="Battery maximum state of charge [%]."
|
||||
)
|
||||
battery_charging_efficiency: Optional[float] = Field(
|
||||
default=None, description="Battery charging efficiency [%]."
|
||||
)
|
||||
battery_discharging_efficiency: Optional[float] = Field(
|
||||
default=None, description="Battery discharging efficiency [%]."
|
||||
)
|
||||
battery_max_charging_power: Optional[int] = Field(
|
||||
default=None, description="Battery maximum charge power [W]."
|
||||
)
|
||||
|
||||
# Battery Electric Vehicle
|
||||
# ------------------------
|
||||
bev_provider: Optional[str] = Field(
|
||||
default=None, description="Id of Battery Electric Vehicle simulation provider."
|
||||
)
|
||||
bev_capacity: Optional[int] = Field(
|
||||
default=None, description="Battery Electric Vehicle capacity [Wh]."
|
||||
)
|
||||
bev_initial_soc: Optional[int] = Field(
|
||||
default=None, description="Battery Electric Vehicle initial state of charge [%]."
|
||||
)
|
||||
bev_soc_max: Optional[int] = Field(
|
||||
default=None, description="Battery Electric Vehicle maximum state of charge [%]."
|
||||
)
|
||||
bev_charging_efficiency: Optional[float] = Field(
|
||||
default=None, description="Battery Electric Vehicle charging efficiency [%]."
|
||||
)
|
||||
bev_discharging_efficiency: Optional[float] = Field(
|
||||
default=None, description="Battery Electric Vehicle discharging efficiency [%]."
|
||||
)
|
||||
bev_max_charging_power: Optional[int] = Field(
|
||||
default=None, description="Battery Electric Vehicle maximum charge power [W]."
|
||||
)
|
||||
|
||||
# Home Appliance - Dish Washer
|
||||
# ----------------------------
|
||||
dishwasher_provider: Optional[str] = Field(
|
||||
default=None, description="Id of Dish Washer simulation provider."
|
||||
)
|
||||
dishwasher_consumption: Optional[int] = Field(
|
||||
default=None, description="Dish Washer energy consumption [Wh]."
|
||||
)
|
||||
dishwasher_duration: Optional[int] = Field(
|
||||
default=None, description="Dish Washer usage duration [h]."
|
||||
)
|
||||
|
||||
# PV Inverter
|
||||
# -----------
|
||||
inverter_provider: Optional[str] = Field(
|
||||
default=None, description="Id of PV Inverter simulation provider."
|
||||
)
|
||||
inverter_power_max: Optional[float] = Field(
|
||||
default=None, description="Inverter maximum power [W]."
|
||||
)
|
||||
|
||||
|
||||
class Devices(SingletonMixin, DevicesBase):
|
||||
# Results of the devices simulation and
|
||||
# insights into various parameters over the entire forecast period.
|
||||
# -----------------------------------------------------------------
|
||||
last_wh_pro_stunde: Optional[NDArray[Shape["*"], float]] = Field(
|
||||
default=None, description="The load in watt-hours per hour."
|
||||
)
|
||||
eauto_soc_pro_stunde: Optional[NDArray[Shape["*"], float]] = Field(
|
||||
default=None, description="The state of charge of the EV for each hour."
|
||||
)
|
||||
einnahmen_euro_pro_stunde: Optional[NDArray[Shape["*"], float]] = Field(
|
||||
default=None,
|
||||
description="The revenue from grid feed-in or other sources in euros per hour.",
|
||||
)
|
||||
home_appliance_wh_per_hour: Optional[NDArray[Shape["*"], float]] = Field(
|
||||
default=None,
|
||||
description="The energy consumption of a household appliance in watt-hours per hour.",
|
||||
)
|
||||
kosten_euro_pro_stunde: Optional[NDArray[Shape["*"], float]] = Field(
|
||||
default=None, description="The costs in euros per hour."
|
||||
)
|
||||
grid_import_wh_pro_stunde: Optional[NDArray[Shape["*"], float]] = Field(
|
||||
default=None, description="The grid energy drawn in watt-hours per hour."
|
||||
)
|
||||
grid_export_wh_pro_stunde: Optional[NDArray[Shape["*"], float]] = Field(
|
||||
default=None, description="The energy fed into the grid in watt-hours per hour."
|
||||
)
|
||||
verluste_wh_pro_stunde: Optional[NDArray[Shape["*"], float]] = Field(
|
||||
default=None, description="The losses in watt-hours per hour."
|
||||
)
|
||||
akku_soc_pro_stunde: Optional[NDArray[Shape["*"], float]] = Field(
|
||||
default=None,
|
||||
description="The state of charge of the battery (not the EV) in percentage per hour.",
|
||||
)
|
||||
def __init__(self, settings: Optional[DevicesCommonSettings] = None):
|
||||
if hasattr(self, "_initialized"):
|
||||
return
|
||||
super().__init__()
|
||||
if settings is None:
|
||||
settings = self.config.devices
|
||||
if settings is None:
|
||||
return
|
||||
|
||||
# Computed fields
|
||||
@computed_field # type: ignore[prop-decorator]
|
||||
@property
|
||||
def total_balance_euro(self) -> float:
|
||||
"""The total balance of revenues minus costs in euros."""
|
||||
return self.total_revenues_euro - self.total_costs_euro
|
||||
# initialize devices
|
||||
if settings.batteries is not None:
|
||||
for battery_params in settings.batteries:
|
||||
self.add_device(Battery(battery_params))
|
||||
if settings.inverters is not None:
|
||||
for inverter_params in settings.inverters:
|
||||
self.add_device(Inverter(inverter_params))
|
||||
if settings.home_appliances is not None:
|
||||
for home_appliance_params in settings.home_appliances:
|
||||
self.add_device(HomeAppliance(home_appliance_params))
|
||||
|
||||
@computed_field # type: ignore[prop-decorator]
|
||||
@property
|
||||
def total_revenues_euro(self) -> float:
|
||||
"""The total revenues in euros."""
|
||||
if self.einnahmen_euro_pro_stunde is None:
|
||||
return 0
|
||||
return np.nansum(self.einnahmen_euro_pro_stunde)
|
||||
self.post_setup()
|
||||
|
||||
@computed_field # type: ignore[prop-decorator]
|
||||
@property
|
||||
def total_costs_euro(self) -> float:
|
||||
"""The total costs in euros."""
|
||||
if self.kosten_euro_pro_stunde is None:
|
||||
return 0
|
||||
return np.nansum(self.kosten_euro_pro_stunde)
|
||||
def post_setup(self) -> None:
|
||||
for device in self.devices.values():
|
||||
device.post_setup()
|
||||
|
||||
@computed_field # type: ignore[prop-decorator]
|
||||
@property
|
||||
def total_losses_wh(self) -> float:
|
||||
"""The total losses in watt-hours over the entire period."""
|
||||
if self.verluste_wh_pro_stunde is None:
|
||||
return 0
|
||||
return np.nansum(self.verluste_wh_pro_stunde)
|
||||
|
||||
# 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
|
||||
# # 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.
|
||||
|
@@ -1,22 +1,46 @@
|
||||
"""Abstract and base classes for devices."""
|
||||
|
||||
from typing import Optional
|
||||
from enum import Enum
|
||||
from typing import Optional, Type
|
||||
|
||||
from pendulum import DateTime
|
||||
from pydantic import ConfigDict, computed_field
|
||||
from pydantic import Field, computed_field
|
||||
|
||||
from akkudoktoreos.core.coreabc import (
|
||||
ConfigMixin,
|
||||
DevicesMixin,
|
||||
EnergyManagementSystemMixin,
|
||||
PredictionMixin,
|
||||
)
|
||||
from akkudoktoreos.core.logging import get_logger
|
||||
from akkudoktoreos.core.pydantic import PydanticBaseModel
|
||||
from akkudoktoreos.core.pydantic import ParametersBaseModel
|
||||
from akkudoktoreos.utils.datetimeutil import to_duration
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
|
||||
# class DeviceParameters(PydanticBaseModel):
|
||||
class DeviceParameters(ParametersBaseModel):
|
||||
device_id: str = Field(description="ID of device")
|
||||
hours: Optional[int] = Field(
|
||||
default=None,
|
||||
gt=0,
|
||||
description="Number of prediction hours. Defaults to global config prediction hours.",
|
||||
)
|
||||
|
||||
|
||||
# class DeviceOptimizeResult(PydanticBaseModel):
|
||||
class DeviceOptimizeResult(ParametersBaseModel):
|
||||
device_id: str = Field(description="ID of device")
|
||||
hours: int = Field(gt=0, description="Number of hours in the simulation.")
|
||||
|
||||
|
||||
class DeviceState(Enum):
|
||||
UNINITIALIZED = 0
|
||||
PREPARED = 1
|
||||
INITIALIZED = 2
|
||||
|
||||
|
||||
class DevicesStartEndMixin(ConfigMixin, EnergyManagementSystemMixin):
|
||||
"""A mixin to manage start, end datetimes for devices data.
|
||||
|
||||
@@ -35,9 +59,9 @@ class DevicesStartEndMixin(ConfigMixin, EnergyManagementSystemMixin):
|
||||
Returns:
|
||||
Optional[DateTime]: The calculated end datetime, or `None` if inputs are missing.
|
||||
"""
|
||||
if self.ems.start_datetime and self.config.prediction_hours:
|
||||
if self.ems.start_datetime and self.config.prediction.prediction_hours:
|
||||
end_datetime = self.ems.start_datetime + to_duration(
|
||||
f"{self.config.prediction_hours} hours"
|
||||
f"{self.config.prediction.prediction_hours} hours"
|
||||
)
|
||||
dst_change = end_datetime.offset_hours - self.ems.start_datetime.offset_hours
|
||||
logger.debug(
|
||||
@@ -68,33 +92,92 @@ class DevicesStartEndMixin(ConfigMixin, EnergyManagementSystemMixin):
|
||||
return int(duration.total_hours())
|
||||
|
||||
|
||||
class DeviceBase(DevicesStartEndMixin, PredictionMixin):
|
||||
class DeviceBase(DevicesStartEndMixin, PredictionMixin, DevicesMixin):
|
||||
"""Base class for device simulations.
|
||||
|
||||
Enables access to EOS configuration data (attribute `config`) and EOS prediction data (attribute
|
||||
`prediction`).
|
||||
Enables access to EOS configuration data (attribute `config`), EOS prediction data (attribute
|
||||
`prediction`) and EOS device registry (attribute `devices`).
|
||||
|
||||
Note:
|
||||
Validation on assignment of the Pydantic model is disabled to speed up simulation runs.
|
||||
Behavior:
|
||||
- Several initialization phases (setup, post_setup):
|
||||
- setup: Initialize class attributes from DeviceParameters (pydantic input validation)
|
||||
- post_setup: Set connections between devices
|
||||
- NotImplemented:
|
||||
- hooks during optimization
|
||||
|
||||
Notes:
|
||||
- This class is base to concrete devices like battery, inverter, etc. that are used in optimization.
|
||||
- Not a pydantic model for a low footprint during optimization.
|
||||
"""
|
||||
|
||||
# Disable validation on assignment to speed up simulation runs.
|
||||
model_config = ConfigDict(
|
||||
validate_assignment=False,
|
||||
)
|
||||
def __init__(self, parameters: Optional[DeviceParameters] = None):
|
||||
self.device_id: str = "<invalid>"
|
||||
self.parameters: Optional[DeviceParameters] = None
|
||||
self.hours = -1
|
||||
if self.total_hours is not None:
|
||||
self.hours = self.total_hours
|
||||
|
||||
self.initialized = DeviceState.UNINITIALIZED
|
||||
|
||||
if parameters is not None:
|
||||
self.setup(parameters)
|
||||
|
||||
def setup(self, parameters: DeviceParameters) -> None:
|
||||
if self.initialized != DeviceState.UNINITIALIZED:
|
||||
return
|
||||
|
||||
self.parameters = parameters
|
||||
self.device_id = self.parameters.device_id
|
||||
|
||||
if self.parameters.hours is not None:
|
||||
self.hours = self.parameters.hours
|
||||
if self.hours < 0:
|
||||
raise ValueError("hours is unset")
|
||||
|
||||
self._setup()
|
||||
|
||||
self.initialized = DeviceState.PREPARED
|
||||
|
||||
def post_setup(self) -> None:
|
||||
if self.initialized.value >= DeviceState.INITIALIZED.value:
|
||||
return
|
||||
|
||||
self._post_setup()
|
||||
self.initialized = DeviceState.INITIALIZED
|
||||
|
||||
def _setup(self) -> None:
|
||||
"""Implement custom setup in derived device classes."""
|
||||
pass
|
||||
|
||||
def _post_setup(self) -> None:
|
||||
"""Implement custom setup in derived device classes that is run when all devices are initialized."""
|
||||
pass
|
||||
|
||||
|
||||
class DevicesBase(DevicesStartEndMixin, PredictionMixin, PydanticBaseModel):
|
||||
class DevicesBase(DevicesStartEndMixin, PredictionMixin):
|
||||
"""Base class for handling device data.
|
||||
|
||||
Enables access to EOS configuration data (attribute `config`) and EOS prediction data (attribute
|
||||
`prediction`).
|
||||
|
||||
Note:
|
||||
Validation on assignment of the Pydantic model is disabled to speed up simulation runs.
|
||||
"""
|
||||
|
||||
# Disable validation on assignment to speed up simulation runs.
|
||||
model_config = ConfigDict(
|
||||
validate_assignment=False,
|
||||
)
|
||||
def __init__(self) -> None:
|
||||
super().__init__()
|
||||
self.devices: dict[str, "DeviceBase"] = dict()
|
||||
|
||||
def get_device_by_id(self, device_id: str) -> Optional["DeviceBase"]:
|
||||
return self.devices.get(device_id)
|
||||
|
||||
def add_device(self, device: Optional["DeviceBase"]) -> None:
|
||||
if device is None:
|
||||
return
|
||||
assert device.device_id not in self.devices, f"{device.device_id} already registered"
|
||||
self.devices[device.device_id] = device
|
||||
|
||||
def remove_device(self, device: Type["DeviceBase"] | str) -> bool:
|
||||
if isinstance(device, DeviceBase):
|
||||
device = device.device_id
|
||||
return self.devices.pop(device, None) is not None # type: ignore[arg-type]
|
||||
|
||||
def reset(self) -> None:
|
||||
self.devices = dict()
|
||||
|
@@ -4,13 +4,13 @@ import numpy as np
|
||||
from pydantic import Field
|
||||
|
||||
from akkudoktoreos.core.logging import get_logger
|
||||
from akkudoktoreos.core.pydantic import ParametersBaseModel
|
||||
from akkudoktoreos.devices.devicesabc import DeviceBase
|
||||
from akkudoktoreos.devices.devicesabc import DeviceBase, DeviceParameters
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
|
||||
class HomeApplianceParameters(ParametersBaseModel):
|
||||
class HomeApplianceParameters(DeviceParameters):
|
||||
device_id: str = Field(description="ID of home appliance")
|
||||
consumption_wh: int = Field(
|
||||
gt=0,
|
||||
description="An integer representing the energy consumption of a household device in watt-hours.",
|
||||
@@ -25,46 +25,15 @@ class HomeAppliance(DeviceBase):
|
||||
def __init__(
|
||||
self,
|
||||
parameters: Optional[HomeApplianceParameters] = None,
|
||||
hours: Optional[int] = 24,
|
||||
provider_id: Optional[str] = None,
|
||||
):
|
||||
# Configuration initialisation
|
||||
self.provider_id = provider_id
|
||||
self.prefix = "<invalid>"
|
||||
if self.provider_id == "GenericDishWasher":
|
||||
self.prefix = "dishwasher"
|
||||
# Parameter initialisiation
|
||||
self.parameters = parameters
|
||||
if hours is None:
|
||||
self.hours = self.total_hours
|
||||
else:
|
||||
self.hours = hours
|
||||
self.parameters: Optional[HomeApplianceParameters] = None
|
||||
super().__init__(parameters)
|
||||
|
||||
self.initialised = False
|
||||
# Run setup if parameters are given, otherwise setup() has to be called later when the config is initialised.
|
||||
if self.parameters is not None:
|
||||
self.setup()
|
||||
|
||||
def setup(self) -> None:
|
||||
if self.initialised:
|
||||
return
|
||||
if self.provider_id is not None:
|
||||
# Setup by configuration
|
||||
self.hours = self.total_hours
|
||||
self.consumption_wh = getattr(self.config, f"{self.prefix}_consumption")
|
||||
self.duration_h = getattr(self.config, f"{self.prefix}_duration")
|
||||
elif self.parameters is not None:
|
||||
# Setup by parameters
|
||||
self.consumption_wh = (
|
||||
self.parameters.consumption_wh
|
||||
) # Total energy consumption of the device in kWh
|
||||
self.duration_h = self.parameters.duration_h # Duration of use in hours
|
||||
else:
|
||||
error_msg = "Parameters and provider ID missing. Can't instantiate."
|
||||
logger.error(error_msg)
|
||||
raise ValueError(error_msg)
|
||||
def _setup(self) -> None:
|
||||
assert self.parameters is not None
|
||||
self.load_curve = np.zeros(self.hours) # Initialize the load curve with zeros
|
||||
self.initialised = True
|
||||
self.duration_h = self.parameters.duration_h
|
||||
self.consumption_wh = self.parameters.consumption_wh
|
||||
|
||||
def set_starting_time(self, start_hour: int, global_start_hour: int = 0) -> None:
|
||||
"""Sets the start time of the device and generates the corresponding load curve.
|
||||
|
@@ -1,64 +1,44 @@
|
||||
from typing import Optional
|
||||
|
||||
from pydantic import Field
|
||||
from scipy.interpolate import RegularGridInterpolator
|
||||
|
||||
from akkudoktoreos.core.logging import get_logger
|
||||
from akkudoktoreos.core.pydantic import ParametersBaseModel
|
||||
from akkudoktoreos.devices.battery import Battery
|
||||
from akkudoktoreos.devices.devicesabc import DeviceBase
|
||||
from akkudoktoreos.devices.devicesabc import DeviceBase, DeviceParameters
|
||||
from akkudoktoreos.prediction.interpolator import get_eos_load_interpolator
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
|
||||
class InverterParameters(ParametersBaseModel):
|
||||
class InverterParameters(DeviceParameters):
|
||||
device_id: str = Field(description="ID of inverter")
|
||||
max_power_wh: float = Field(gt=0)
|
||||
battery: Optional[str] = Field(default=None, description="ID of battery")
|
||||
|
||||
|
||||
class Inverter(DeviceBase):
|
||||
def __init__(
|
||||
self,
|
||||
self_consumption_predictor: RegularGridInterpolator,
|
||||
parameters: Optional[InverterParameters] = None,
|
||||
battery: Optional[Battery] = None,
|
||||
provider_id: Optional[str] = None,
|
||||
):
|
||||
# Configuration initialisation
|
||||
self.provider_id = provider_id
|
||||
self.prefix = "<invalid>"
|
||||
if self.provider_id == "GenericInverter":
|
||||
self.prefix = "inverter"
|
||||
# Parameter initialisiation
|
||||
self.parameters = parameters
|
||||
if battery is None:
|
||||
self.parameters: Optional[InverterParameters] = None
|
||||
super().__init__(parameters)
|
||||
|
||||
def _setup(self) -> None:
|
||||
assert self.parameters is not None
|
||||
if self.parameters.battery is None:
|
||||
# For the moment raise exception
|
||||
# TODO: Make battery configurable by config
|
||||
error_msg = "Battery for PV inverter is mandatory."
|
||||
logger.error(error_msg)
|
||||
raise NotImplementedError(error_msg)
|
||||
self.battery = battery # Connection to a battery object
|
||||
self.self_consumption_predictor = self_consumption_predictor
|
||||
self.self_consumption_predictor = get_eos_load_interpolator()
|
||||
self.max_power_wh = (
|
||||
self.parameters.max_power_wh
|
||||
) # Maximum power that the inverter can handle
|
||||
|
||||
self.initialised = False
|
||||
# Run setup if parameters are given, otherwise setup() has to be called later when the config is initialised.
|
||||
if self.parameters is not None:
|
||||
self.setup()
|
||||
|
||||
def setup(self) -> None:
|
||||
if self.initialised:
|
||||
return
|
||||
if self.provider_id is not None:
|
||||
# Setup by configuration
|
||||
self.max_power_wh = getattr(self.config, f"{self.prefix}_power_max")
|
||||
elif self.parameters is not None:
|
||||
# Setup by parameters
|
||||
self.max_power_wh = (
|
||||
self.parameters.max_power_wh # Maximum power that the inverter can handle
|
||||
)
|
||||
else:
|
||||
error_msg = "Parameters and provider ID missing. Can't instantiate."
|
||||
logger.error(error_msg)
|
||||
raise ValueError(error_msg)
|
||||
def _post_setup(self) -> None:
|
||||
assert self.parameters is not None
|
||||
self.battery = self.devices.get_device_by_id(self.parameters.battery)
|
||||
|
||||
def process_energy(
|
||||
self, generation: float, consumption: float, hour: int
|
||||
|
25
src/akkudoktoreos/devices/settings.py
Normal file
25
src/akkudoktoreos/devices/settings.py
Normal file
@@ -0,0 +1,25 @@
|
||||
from typing import Optional
|
||||
|
||||
from pydantic import Field
|
||||
|
||||
from akkudoktoreos.config.configabc import SettingsBaseModel
|
||||
from akkudoktoreos.core.logging import get_logger
|
||||
from akkudoktoreos.devices.battery import BaseBatteryParameters
|
||||
from akkudoktoreos.devices.generic import HomeApplianceParameters
|
||||
from akkudoktoreos.devices.inverter import InverterParameters
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
|
||||
class DevicesCommonSettings(SettingsBaseModel):
|
||||
"""Base configuration for devices simulation settings."""
|
||||
|
||||
batteries: Optional[list[BaseBatteryParameters]] = Field(
|
||||
default=None, description="List of battery/ev devices"
|
||||
)
|
||||
inverters: Optional[list[InverterParameters]] = Field(
|
||||
default=None, description="List of inverters"
|
||||
)
|
||||
home_appliances: Optional[list[HomeApplianceParameters]] = Field(
|
||||
default=None, description="List of home appliances"
|
||||
)
|
Reference in New Issue
Block a user