mirror of
https://github.com/Akkudoktor-EOS/EOS.git
synced 2025-09-13 07:21:16 +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:
@@ -64,6 +64,25 @@ def config_mixin(config_eos):
|
||||
yield config_mixin_patch
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def devices_eos(config_mixin):
|
||||
from akkudoktoreos.devices.devices import get_devices
|
||||
|
||||
devices = get_devices()
|
||||
print("devices_eos reset!")
|
||||
devices.reset()
|
||||
return devices
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def devices_mixin(devices_eos):
|
||||
with patch(
|
||||
"akkudoktoreos.core.coreabc.DevicesMixin.devices", new_callable=PropertyMock
|
||||
) as devices_mixin_patch:
|
||||
devices_mixin_patch.return_value = devices_eos
|
||||
yield devices_mixin_patch
|
||||
|
||||
|
||||
# Test if test has side effect of writing to system (user) config file
|
||||
# Before activating, make sure that no user config file exists (e.g. ~/.config/net.akkudoktoreos.eos/EOS.config.json)
|
||||
@pytest.fixture(autouse=True)
|
||||
@@ -114,8 +133,12 @@ def config_eos(
|
||||
monkeypatch,
|
||||
) -> ConfigEOS:
|
||||
"""Fixture to reset EOS config to default values."""
|
||||
monkeypatch.setenv("data_cache_subpath", str(config_default_dirs[-1] / "data/cache"))
|
||||
monkeypatch.setenv("data_output_subpath", str(config_default_dirs[-1] / "data/output"))
|
||||
monkeypatch.setenv(
|
||||
"EOS_CONFIG__DATA_CACHE_SUBPATH", str(config_default_dirs[-1] / "data/cache")
|
||||
)
|
||||
monkeypatch.setenv(
|
||||
"EOS_CONFIG__DATA_OUTPUT_SUBPATH", str(config_default_dirs[-1] / "data/output")
|
||||
)
|
||||
config_file = config_default_dirs[0] / ConfigEOS.CONFIG_FILE_NAME
|
||||
config_file_cwd = config_default_dirs[1] / ConfigEOS.CONFIG_FILE_NAME
|
||||
assert not config_file.exists()
|
||||
@@ -125,9 +148,9 @@ def config_eos(
|
||||
assert config_file == config_eos.config_file_path
|
||||
assert config_file.exists()
|
||||
assert not config_file_cwd.exists()
|
||||
assert config_default_dirs[-1] / "data" == config_eos.data_folder_path
|
||||
assert config_default_dirs[-1] / "data/cache" == config_eos.data_cache_path
|
||||
assert config_default_dirs[-1] / "data/output" == config_eos.data_output_path
|
||||
assert config_default_dirs[-1] / "data" == config_eos.general.data_folder_path
|
||||
assert config_default_dirs[-1] / "data/cache" == config_eos.general.data_cache_path
|
||||
assert config_default_dirs[-1] / "data/output" == config_eos.general.data_output_path
|
||||
return config_eos
|
||||
|
||||
|
||||
@@ -166,6 +189,7 @@ def server(xprocess, config_eos, config_default_dirs):
|
||||
# Set environment before any subprocess run, to keep custom config dir
|
||||
env = os.environ.copy()
|
||||
env["EOS_DIR"] = str(config_default_dirs[-1])
|
||||
project_dir = config_eos.package_root_path
|
||||
|
||||
# assure server to be installed
|
||||
try:
|
||||
@@ -175,9 +199,9 @@ def server(xprocess, config_eos, config_default_dirs):
|
||||
env=env,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
cwd=project_dir,
|
||||
)
|
||||
except subprocess.CalledProcessError:
|
||||
project_dir = config_eos.package_root_path
|
||||
subprocess.run(
|
||||
[sys.executable, "-m", "pip", "install", "-e", project_dir],
|
||||
check=True,
|
||||
|
@@ -7,13 +7,15 @@ from akkudoktoreos.devices.battery import Battery, SolarPanelBatteryParameters
|
||||
@pytest.fixture
|
||||
def setup_pv_battery():
|
||||
params = SolarPanelBatteryParameters(
|
||||
device_id="battery1",
|
||||
capacity_wh=10000,
|
||||
initial_soc_percentage=50,
|
||||
min_soc_percentage=20,
|
||||
max_soc_percentage=80,
|
||||
max_charge_power_w=8000,
|
||||
hours=24,
|
||||
)
|
||||
battery = Battery(params, hours=24)
|
||||
battery = Battery(params)
|
||||
battery.reset()
|
||||
return battery
|
||||
|
||||
@@ -113,7 +115,6 @@ def test_soc_limits(setup_pv_battery):
|
||||
|
||||
def test_max_charge_power_w(setup_pv_battery):
|
||||
battery = setup_pv_battery
|
||||
battery.setup()
|
||||
assert (
|
||||
battery.parameters.max_charge_power_w == 8000
|
||||
), "Default max charge power should be 5000W, We ask for 8000W here"
|
||||
@@ -121,7 +122,6 @@ def test_max_charge_power_w(setup_pv_battery):
|
||||
|
||||
def test_charge_energy_within_limits(setup_pv_battery):
|
||||
battery = setup_pv_battery
|
||||
battery.setup()
|
||||
initial_soc_wh = battery.soc_wh
|
||||
|
||||
charged_wh, losses_wh = battery.charge_energy(wh=4000, hour=1)
|
||||
@@ -134,7 +134,6 @@ def test_charge_energy_within_limits(setup_pv_battery):
|
||||
|
||||
def test_charge_energy_exceeds_capacity(setup_pv_battery):
|
||||
battery = setup_pv_battery
|
||||
battery.setup()
|
||||
initial_soc_wh = battery.soc_wh
|
||||
|
||||
# Try to overcharge beyond max capacity
|
||||
@@ -149,7 +148,6 @@ def test_charge_energy_exceeds_capacity(setup_pv_battery):
|
||||
|
||||
def test_charge_energy_not_allowed_hour(setup_pv_battery):
|
||||
battery = setup_pv_battery
|
||||
battery.setup()
|
||||
|
||||
# Disable charging for all hours
|
||||
battery.set_charge_per_hour(np.zeros(battery.hours))
|
||||
@@ -165,7 +163,6 @@ def test_charge_energy_not_allowed_hour(setup_pv_battery):
|
||||
|
||||
def test_charge_energy_relative_power(setup_pv_battery):
|
||||
battery = setup_pv_battery
|
||||
battery.setup()
|
||||
|
||||
relative_power = 0.5 # 50% of max charge power
|
||||
charged_wh, losses_wh = battery.charge_energy(wh=None, hour=4, relative_power=relative_power)
|
||||
@@ -183,13 +180,15 @@ def setup_car_battery():
|
||||
from akkudoktoreos.devices.battery import ElectricVehicleParameters
|
||||
|
||||
params = ElectricVehicleParameters(
|
||||
device_id="ev1",
|
||||
capacity_wh=40000,
|
||||
initial_soc_percentage=60,
|
||||
min_soc_percentage=10,
|
||||
max_soc_percentage=90,
|
||||
max_charge_power_w=7000,
|
||||
hours=24,
|
||||
)
|
||||
battery = Battery(params, hours=24)
|
||||
battery = Battery(params)
|
||||
battery.reset()
|
||||
return battery
|
||||
|
||||
|
@@ -1,5 +1,3 @@
|
||||
from pathlib import Path
|
||||
|
||||
import numpy as np
|
||||
import pytest
|
||||
|
||||
@@ -16,58 +14,58 @@ from akkudoktoreos.devices.battery import (
|
||||
)
|
||||
from akkudoktoreos.devices.generic import HomeAppliance, HomeApplianceParameters
|
||||
from akkudoktoreos.devices.inverter import Inverter, InverterParameters
|
||||
from akkudoktoreos.prediction.interpolator import SelfConsumptionProbabilityInterpolator
|
||||
|
||||
start_hour = 1
|
||||
|
||||
|
||||
# Example initialization of necessary components
|
||||
@pytest.fixture
|
||||
def create_ems_instance(config_eos) -> EnergieManagementSystem:
|
||||
def create_ems_instance(devices_eos, config_eos) -> EnergieManagementSystem:
|
||||
"""Fixture to create an EnergieManagementSystem instance with given test parameters."""
|
||||
# Assure configuration holds the correct values
|
||||
config_eos.merge_settings_from_dict({"prediction_hours": 48, "optimization_hours": 24})
|
||||
assert config_eos.prediction_hours is not None
|
||||
config_eos.merge_settings_from_dict(
|
||||
{"prediction": {"prediction_hours": 48}, "optimization": {"optimization_hours": 24}}
|
||||
)
|
||||
assert config_eos.prediction.prediction_hours == 48
|
||||
|
||||
# Initialize the battery and the inverter
|
||||
akku = Battery(
|
||||
SolarPanelBatteryParameters(
|
||||
capacity_wh=5000, initial_soc_percentage=80, min_soc_percentage=10
|
||||
),
|
||||
hours=config_eos.prediction_hours,
|
||||
device_id="battery1",
|
||||
capacity_wh=5000,
|
||||
initial_soc_percentage=80,
|
||||
min_soc_percentage=10,
|
||||
)
|
||||
)
|
||||
|
||||
# 1h Load to Sub 1h Load Distribution -> SelfConsumptionRate
|
||||
sc = SelfConsumptionProbabilityInterpolator(
|
||||
Path(__file__).parent.resolve()
|
||||
/ ".."
|
||||
/ "src"
|
||||
/ "akkudoktoreos"
|
||||
/ "data"
|
||||
/ "regular_grid_interpolator.pkl"
|
||||
)
|
||||
|
||||
akku.reset()
|
||||
inverter = Inverter(sc, InverterParameters(max_power_wh=10000), akku)
|
||||
devices_eos.add_device(akku)
|
||||
|
||||
inverter = Inverter(
|
||||
InverterParameters(device_id="inverter1", max_power_wh=10000, battery=akku.device_id)
|
||||
)
|
||||
devices_eos.add_device(inverter)
|
||||
|
||||
# Household device (currently not used, set to None)
|
||||
home_appliance = HomeAppliance(
|
||||
HomeApplianceParameters(
|
||||
device_id="dishwasher1",
|
||||
consumption_wh=2000,
|
||||
duration_h=2,
|
||||
),
|
||||
hours=config_eos.prediction_hours,
|
||||
)
|
||||
home_appliance.set_starting_time(2)
|
||||
devices_eos.add_device(home_appliance)
|
||||
|
||||
# Example initialization of electric car battery
|
||||
eauto = Battery(
|
||||
ElectricVehicleParameters(
|
||||
capacity_wh=26400, initial_soc_percentage=10, min_soc_percentage=10
|
||||
device_id="ev1", capacity_wh=26400, initial_soc_percentage=10, min_soc_percentage=10
|
||||
),
|
||||
hours=config_eos.prediction_hours,
|
||||
)
|
||||
eauto.set_charge_per_hour(np.full(config_eos.prediction_hours, 1))
|
||||
eauto.set_charge_per_hour(np.full(config_eos.prediction.prediction_hours, 1))
|
||||
devices_eos.add_device(eauto)
|
||||
|
||||
devices_eos.post_setup()
|
||||
|
||||
# Parameters based on previous example data
|
||||
pv_prognose_wh = [
|
||||
|
@@ -1,5 +1,3 @@
|
||||
from pathlib import Path
|
||||
|
||||
import numpy as np
|
||||
import pytest
|
||||
|
||||
@@ -15,64 +13,61 @@ from akkudoktoreos.devices.battery import (
|
||||
)
|
||||
from akkudoktoreos.devices.generic import HomeAppliance, HomeApplianceParameters
|
||||
from akkudoktoreos.devices.inverter import Inverter, InverterParameters
|
||||
from akkudoktoreos.prediction.interpolator import SelfConsumptionProbabilityInterpolator
|
||||
|
||||
start_hour = 0
|
||||
|
||||
|
||||
# Example initialization of necessary components
|
||||
@pytest.fixture
|
||||
def create_ems_instance(config_eos) -> EnergieManagementSystem:
|
||||
def create_ems_instance(devices_eos, config_eos) -> EnergieManagementSystem:
|
||||
"""Fixture to create an EnergieManagementSystem instance with given test parameters."""
|
||||
# Assure configuration holds the correct values
|
||||
config_eos.merge_settings_from_dict({"prediction_hours": 48, "optimization_hours": 24})
|
||||
assert config_eos.prediction_hours is not None
|
||||
config_eos.merge_settings_from_dict(
|
||||
{"prediction": {"prediction_hours": 48}, "optimization": {"optimization_hours": 24}}
|
||||
)
|
||||
assert config_eos.prediction.prediction_hours == 48
|
||||
|
||||
# Initialize the battery and the inverter
|
||||
akku = Battery(
|
||||
SolarPanelBatteryParameters(
|
||||
capacity_wh=5000, initial_soc_percentage=80, min_soc_percentage=10
|
||||
),
|
||||
hours=config_eos.prediction_hours,
|
||||
device_id="pv1", capacity_wh=5000, initial_soc_percentage=80, min_soc_percentage=10
|
||||
)
|
||||
)
|
||||
|
||||
# 1h Load to Sub 1h Load Distribution -> SelfConsumptionRate
|
||||
sc = SelfConsumptionProbabilityInterpolator(
|
||||
Path(__file__).parent.resolve()
|
||||
/ ".."
|
||||
/ "src"
|
||||
/ "akkudoktoreos"
|
||||
/ "data"
|
||||
/ "regular_grid_interpolator.pkl"
|
||||
)
|
||||
|
||||
akku.reset()
|
||||
inverter = Inverter(sc, InverterParameters(max_power_wh=10000), akku)
|
||||
devices_eos.add_device(akku)
|
||||
|
||||
inverter = Inverter(
|
||||
InverterParameters(device_id="iv1", max_power_wh=10000, battery=akku.device_id)
|
||||
)
|
||||
devices_eos.add_device(inverter)
|
||||
|
||||
# Household device (currently not used, set to None)
|
||||
home_appliance = HomeAppliance(
|
||||
HomeApplianceParameters(
|
||||
device_id="dishwasher1",
|
||||
consumption_wh=2000,
|
||||
duration_h=2,
|
||||
),
|
||||
hours=config_eos.prediction_hours,
|
||||
)
|
||||
)
|
||||
home_appliance.set_starting_time(2)
|
||||
devices_eos.add_device(home_appliance)
|
||||
|
||||
# Example initialization of electric car battery
|
||||
eauto = Battery(
|
||||
ElectricVehicleParameters(
|
||||
capacity_wh=26400, initial_soc_percentage=100, min_soc_percentage=100
|
||||
device_id="ev1", capacity_wh=26400, initial_soc_percentage=100, min_soc_percentage=100
|
||||
),
|
||||
hours=config_eos.prediction_hours,
|
||||
)
|
||||
devices_eos.add_device(eauto)
|
||||
|
||||
devices_eos.post_setup()
|
||||
|
||||
# Parameters based on previous example data
|
||||
pv_prognose_wh = [0.0] * config_eos.prediction_hours
|
||||
pv_prognose_wh = [0.0] * config_eos.prediction.prediction_hours
|
||||
pv_prognose_wh[10] = 5000.0
|
||||
pv_prognose_wh[11] = 5000.0
|
||||
|
||||
strompreis_euro_pro_wh = [0.001] * config_eos.prediction_hours
|
||||
strompreis_euro_pro_wh = [0.001] * config_eos.prediction.prediction_hours
|
||||
strompreis_euro_pro_wh[0:10] = [0.00001] * 10
|
||||
strompreis_euro_pro_wh[11:15] = [0.00005] * 4
|
||||
strompreis_euro_pro_wh[20] = 0.00001
|
||||
@@ -146,10 +141,10 @@ def create_ems_instance(config_eos) -> EnergieManagementSystem:
|
||||
home_appliance=home_appliance,
|
||||
)
|
||||
|
||||
ac = np.full(config_eos.prediction_hours, 0.0)
|
||||
ac = np.full(config_eos.prediction.prediction_hours, 0.0)
|
||||
ac[20] = 1
|
||||
ems.set_akku_ac_charge_hours(ac)
|
||||
dc = np.full(config_eos.prediction_hours, 0.0)
|
||||
dc = np.full(config_eos.prediction.prediction_hours, 0.0)
|
||||
dc[11] = 1
|
||||
ems.set_akku_dc_charge_hours(dc)
|
||||
|
||||
|
@@ -49,7 +49,9 @@ def test_optimize(
|
||||
):
|
||||
"""Test optimierung_ems."""
|
||||
# Assure configuration holds the correct values
|
||||
config_eos.merge_settings_from_dict({"prediction_hours": 48, "optimization_hours": 48})
|
||||
config_eos.merge_settings_from_dict(
|
||||
{"prediction": {"prediction_hours": 48}, "optimization": {"optimization_hours": 48}}
|
||||
)
|
||||
|
||||
# Load input and output data
|
||||
file = DIR_TESTDATA / fn_in
|
||||
|
@@ -38,15 +38,19 @@ def test_config_constants(config_eos):
|
||||
|
||||
def test_computed_paths(config_eos):
|
||||
"""Test computed paths for output and cache."""
|
||||
config_eos.merge_settings_from_dict(
|
||||
{
|
||||
"data_folder_path": "/base/data",
|
||||
"data_output_subpath": "output",
|
||||
"data_cache_subpath": "cache",
|
||||
}
|
||||
)
|
||||
assert config_eos.data_output_path == Path("/base/data/output")
|
||||
assert config_eos.data_cache_path == Path("/base/data/cache")
|
||||
# Don't actually try to create the data folder
|
||||
with patch("pathlib.Path.mkdir"):
|
||||
config_eos.merge_settings_from_dict(
|
||||
{
|
||||
"general": {
|
||||
"data_folder_path": "/base/data",
|
||||
"data_output_subpath": "extra/output",
|
||||
"data_cache_subpath": "somewhere/cache",
|
||||
}
|
||||
}
|
||||
)
|
||||
assert config_eos.general.data_output_path == Path("/base/data/extra/output")
|
||||
assert config_eos.general.data_cache_path == Path("/base/data/somewhere/cache")
|
||||
# reset settings so the config_eos fixture can verify the default paths
|
||||
config_eos.reset_settings()
|
||||
|
||||
@@ -87,7 +91,7 @@ def test_config_file_priority(config_default_dirs):
|
||||
config_file = Path(config_default_dir_user) / ConfigEOS.CONFIG_FILE_NAME
|
||||
config_file.parent.mkdir()
|
||||
config_file.write_text("{}")
|
||||
config_eos = get_config()
|
||||
config_eos.update()
|
||||
assert config_eos.config_file_path == config_file
|
||||
|
||||
|
||||
@@ -141,5 +145,5 @@ def test_config_copy(config_eos, monkeypatch):
|
||||
assert not temp_config_file_path.exists()
|
||||
with patch("akkudoktoreos.config.config.user_config_dir", return_value=temp_dir):
|
||||
assert config_eos._get_config_file_path() == (temp_config_file_path, False)
|
||||
config_eos.from_config_file()
|
||||
config_eos.update()
|
||||
assert temp_config_file_path.exists()
|
||||
|
@@ -23,9 +23,10 @@ FILE_TESTDATA_ELECPRICEAKKUDOKTOR_1_JSON = DIR_TESTDATA.joinpath(
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def elecprice_provider(monkeypatch):
|
||||
def elecprice_provider(monkeypatch, config_eos):
|
||||
"""Fixture to create a ElecPriceProvider instance."""
|
||||
monkeypatch.setenv("elecprice_provider", "ElecPriceAkkudoktor")
|
||||
monkeypatch.setenv("EOS_ELECPRICE__ELECPRICE_PROVIDER", "ElecPriceAkkudoktor")
|
||||
config_eos.reset_settings()
|
||||
return ElecPriceAkkudoktor()
|
||||
|
||||
|
||||
@@ -56,9 +57,9 @@ def test_singleton_instance(elecprice_provider):
|
||||
|
||||
def test_invalid_provider(elecprice_provider, monkeypatch):
|
||||
"""Test requesting an unsupported elecprice_provider."""
|
||||
monkeypatch.setenv("elecprice_provider", "<invalid>")
|
||||
elecprice_provider.config.update()
|
||||
assert elecprice_provider.enabled() == False
|
||||
monkeypatch.setenv("EOS_ELECPRICE__ELECPRICE_PROVIDER", "<invalid>")
|
||||
elecprice_provider.config.reset_settings()
|
||||
assert not elecprice_provider.enabled()
|
||||
|
||||
|
||||
# ------------------------------------------------
|
||||
|
@@ -16,9 +16,13 @@ FILE_TESTDATA_ELECPRICEIMPORT_1_JSON = DIR_TESTDATA.joinpath("import_input_1.jso
|
||||
def elecprice_provider(sample_import_1_json, config_eos):
|
||||
"""Fixture to create a ElecPriceProvider instance."""
|
||||
settings = {
|
||||
"elecprice_provider": "ElecPriceImport",
|
||||
"elecpriceimport_file_path": str(FILE_TESTDATA_ELECPRICEIMPORT_1_JSON),
|
||||
"elecpriceimport_json": json.dumps(sample_import_1_json),
|
||||
"elecprice": {
|
||||
"elecprice_provider": "ElecPriceImport",
|
||||
"provider_settings": {
|
||||
"elecpriceimport_file_path": str(FILE_TESTDATA_ELECPRICEIMPORT_1_JSON),
|
||||
"elecpriceimport_json": json.dumps(sample_import_1_json),
|
||||
},
|
||||
}
|
||||
}
|
||||
config_eos.merge_settings_from_dict(settings)
|
||||
provider = ElecPriceImport()
|
||||
@@ -48,8 +52,12 @@ def test_singleton_instance(elecprice_provider):
|
||||
def test_invalid_provider(elecprice_provider, config_eos):
|
||||
"""Test requesting an unsupported elecprice_provider."""
|
||||
settings = {
|
||||
"elecprice_provider": "<invalid>",
|
||||
"elecpriceimport_file_path": str(FILE_TESTDATA_ELECPRICEIMPORT_1_JSON),
|
||||
"elecprice": {
|
||||
"elecprice_provider": "<invalid>",
|
||||
"provider_settings": {
|
||||
"elecpriceimport_file_path": str(FILE_TESTDATA_ELECPRICEIMPORT_1_JSON),
|
||||
},
|
||||
}
|
||||
}
|
||||
config_eos.merge_settings_from_dict(settings)
|
||||
assert not elecprice_provider.enabled()
|
||||
@@ -78,11 +86,11 @@ def test_import(elecprice_provider, sample_import_1_json, start_datetime, from_f
|
||||
ems_eos = get_ems()
|
||||
ems_eos.set_start_datetime(to_datetime(start_datetime, in_timezone="Europe/Berlin"))
|
||||
if from_file:
|
||||
config_eos.elecpriceimport_json = None
|
||||
assert config_eos.elecpriceimport_json is None
|
||||
config_eos.elecprice.provider_settings.elecpriceimport_json = None
|
||||
assert config_eos.elecprice.provider_settings.elecpriceimport_json is None
|
||||
else:
|
||||
config_eos.elecpriceimport_file_path = None
|
||||
assert config_eos.elecpriceimport_file_path is None
|
||||
config_eos.elecprice.provider_settings.elecpriceimport_file_path = None
|
||||
assert config_eos.elecprice.provider_settings.elecpriceimport_file_path is None
|
||||
elecprice_provider.clear()
|
||||
|
||||
# Call the method
|
||||
|
@@ -1,4 +1,4 @@
|
||||
from unittest.mock import Mock
|
||||
from unittest.mock import Mock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
@@ -6,22 +6,29 @@ from akkudoktoreos.devices.inverter import Inverter, InverterParameters
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_battery():
|
||||
def mock_battery() -> Mock:
|
||||
mock_battery = Mock()
|
||||
mock_battery.charge_energy = Mock(return_value=(0.0, 0.0))
|
||||
mock_battery.discharge_energy = Mock(return_value=(0.0, 0.0))
|
||||
mock_battery.device_id = "battery1"
|
||||
return mock_battery
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def inverter(mock_battery):
|
||||
def inverter(mock_battery, devices_eos) -> Inverter:
|
||||
devices_eos.add_device(mock_battery)
|
||||
mock_self_consumption_predictor = Mock()
|
||||
mock_self_consumption_predictor.calculate_self_consumption.return_value = 1.0
|
||||
return Inverter(
|
||||
mock_self_consumption_predictor,
|
||||
InverterParameters(max_power_wh=500.0),
|
||||
battery=mock_battery,
|
||||
)
|
||||
with patch(
|
||||
"akkudoktoreos.devices.inverter.get_eos_load_interpolator",
|
||||
return_value=mock_self_consumption_predictor,
|
||||
):
|
||||
iv = Inverter(
|
||||
InverterParameters(device_id="iv1", max_power_wh=500.0, battery=mock_battery.device_id),
|
||||
)
|
||||
devices_eos.add_device(iv)
|
||||
devices_eos.post_setup()
|
||||
return iv
|
||||
|
||||
|
||||
def test_process_energy_excess_generation(inverter, mock_battery):
|
||||
|
@@ -17,9 +17,13 @@ from akkudoktoreos.utils.datetimeutil import compare_datetimes, to_datetime, to_
|
||||
def load_provider(config_eos):
|
||||
"""Fixture to initialise the LoadAkkudoktor instance."""
|
||||
settings = {
|
||||
"load_provider": "LoadAkkudoktor",
|
||||
"load_name": "Akkudoktor Profile",
|
||||
"loadakkudoktor_year_energy": "1000",
|
||||
"load": {
|
||||
"load_provider": "LoadAkkudoktor",
|
||||
"provider_settings": {
|
||||
"load_name": "Akkudoktor Profile",
|
||||
"loadakkudoktor_year_energy": "1000",
|
||||
},
|
||||
}
|
||||
}
|
||||
config_eos.merge_settings_from_dict(settings)
|
||||
return LoadAkkudoktor()
|
||||
|
@@ -3,7 +3,11 @@ import pytest
|
||||
from pendulum import datetime, duration
|
||||
|
||||
from akkudoktoreos.config.config import SettingsEOS
|
||||
from akkudoktoreos.measurement.measurement import MeasurementDataRecord, get_measurement
|
||||
from akkudoktoreos.measurement.measurement import (
|
||||
MeasurementCommonSettings,
|
||||
MeasurementDataRecord,
|
||||
get_measurement,
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
@@ -186,8 +190,10 @@ def test_load_total_no_data(measurement_eos):
|
||||
def test_name_to_key(measurement_eos):
|
||||
"""Test name_to_key functionality."""
|
||||
settings = SettingsEOS(
|
||||
measurement_load0_name="Household",
|
||||
measurement_load1_name="Heat Pump",
|
||||
measurement=MeasurementCommonSettings(
|
||||
measurement_load0_name="Household",
|
||||
measurement_load1_name="Heat Pump",
|
||||
)
|
||||
)
|
||||
measurement_eos.config.merge_settings(settings)
|
||||
|
||||
@@ -199,8 +205,10 @@ def test_name_to_key(measurement_eos):
|
||||
def test_name_to_key_invalid_topic(measurement_eos):
|
||||
"""Test name_to_key with an invalid topic."""
|
||||
settings = SettingsEOS(
|
||||
measurement_load0_name="Household",
|
||||
measurement_load1_name="Heat Pump",
|
||||
MeasurementCommonSettings(
|
||||
measurement_load0_name="Household",
|
||||
measurement_load1_name="Heat Pump",
|
||||
)
|
||||
)
|
||||
measurement_eos.config.merge_settings(settings)
|
||||
|
||||
|
@@ -126,9 +126,9 @@ def test_prediction_common_settings_with_location():
|
||||
|
||||
def test_prediction_common_settings_timezone_none_when_coordinates_missing():
|
||||
"""Test that timezone is None when latitude or longitude is missing."""
|
||||
config_no_latitude = PredictionCommonSettings(longitude=-74.0060)
|
||||
config_no_longitude = PredictionCommonSettings(latitude=40.7128)
|
||||
config_no_coords = PredictionCommonSettings()
|
||||
config_no_latitude = PredictionCommonSettings(latitude=None, longitude=-74.0060)
|
||||
config_no_longitude = PredictionCommonSettings(latitude=40.7128, longitude=None)
|
||||
config_no_coords = PredictionCommonSettings(latitude=None, longitude=None)
|
||||
|
||||
assert config_no_latitude.timezone is None
|
||||
assert config_no_longitude.timezone is None
|
||||
|
@@ -88,31 +88,31 @@ class TestPredictionBase:
|
||||
@pytest.fixture
|
||||
def base(self, monkeypatch):
|
||||
# Provide default values for configuration
|
||||
monkeypatch.setenv("latitude", "50.0")
|
||||
monkeypatch.setenv("longitude", "10.0")
|
||||
monkeypatch.setenv("EOS_PREDICTION__LATITUDE", "50.0")
|
||||
monkeypatch.setenv("EOS_PREDICTION__LONGITUDE", "10.0")
|
||||
derived = DerivedBase()
|
||||
derived.config.update()
|
||||
derived.config.reset_settings()
|
||||
return derived
|
||||
|
||||
def test_config_value_from_env_variable(self, base, monkeypatch):
|
||||
# From Prediction Config
|
||||
monkeypatch.setenv("latitude", "2.5")
|
||||
base.config.update()
|
||||
assert base.config.latitude == 2.5
|
||||
monkeypatch.setenv("EOS_PREDICTION__LATITUDE", "2.5")
|
||||
base.config.reset_settings()
|
||||
assert base.config.prediction.latitude == 2.5
|
||||
|
||||
def test_config_value_from_field_default(self, base, monkeypatch):
|
||||
assert base.config.model_fields["prediction_hours"].default == 48
|
||||
assert base.config.prediction_hours == 48
|
||||
monkeypatch.setenv("prediction_hours", "128")
|
||||
base.config.update()
|
||||
assert base.config.prediction_hours == 128
|
||||
monkeypatch.delenv("prediction_hours")
|
||||
base.config.update()
|
||||
assert base.config.prediction_hours == 48
|
||||
assert base.config.prediction.model_fields["prediction_hours"].default == 48
|
||||
assert base.config.prediction.prediction_hours == 48
|
||||
monkeypatch.setenv("EOS_PREDICTION__PREDICTION_HOURS", "128")
|
||||
base.config.reset_settings()
|
||||
assert base.config.prediction.prediction_hours == 128
|
||||
monkeypatch.delenv("EOS_PREDICTION__PREDICTION_HOURS")
|
||||
base.config.reset_settings()
|
||||
assert base.config.prediction.prediction_hours == 48
|
||||
|
||||
def test_get_config_value_key_error(self, base):
|
||||
with pytest.raises(AttributeError):
|
||||
base.config.non_existent_key
|
||||
base.config.prediction.non_existent_key
|
||||
|
||||
|
||||
# TestPredictionRecord fully covered by TestDataRecord
|
||||
@@ -159,14 +159,14 @@ class TestPredictionProvider:
|
||||
"""Test that computed fields `end_datetime` and `keep_datetime` are correctly calculated."""
|
||||
ems_eos = get_ems()
|
||||
ems_eos.set_start_datetime(sample_start_datetime)
|
||||
provider.config.prediction_hours = 24 # 24 hours into the future
|
||||
provider.config.prediction_historic_hours = 48 # 48 hours into the past
|
||||
provider.config.prediction.prediction_hours = 24 # 24 hours into the future
|
||||
provider.config.prediction.prediction_historic_hours = 48 # 48 hours into the past
|
||||
|
||||
expected_end_datetime = sample_start_datetime + to_duration(
|
||||
provider.config.prediction_hours * 3600
|
||||
provider.config.prediction.prediction_hours * 3600
|
||||
)
|
||||
expected_keep_datetime = sample_start_datetime - to_duration(
|
||||
provider.config.prediction_historic_hours * 3600
|
||||
provider.config.prediction.prediction_historic_hours * 3600
|
||||
)
|
||||
|
||||
assert (
|
||||
@@ -183,31 +183,32 @@ class TestPredictionProvider:
|
||||
# EOS config supersedes
|
||||
ems_eos = get_ems()
|
||||
# The following values are currently not set in EOS config, we can override
|
||||
monkeypatch.setenv("prediction_historic_hours", "2")
|
||||
assert os.getenv("prediction_historic_hours") == "2"
|
||||
monkeypatch.setenv("latitude", "37.7749")
|
||||
assert os.getenv("latitude") == "37.7749"
|
||||
monkeypatch.setenv("longitude", "-122.4194")
|
||||
assert os.getenv("longitude") == "-122.4194"
|
||||
monkeypatch.setenv("EOS_PREDICTION__PREDICTION_HISTORIC_HOURS", "2")
|
||||
assert os.getenv("EOS_PREDICTION__PREDICTION_HISTORIC_HOURS") == "2"
|
||||
monkeypatch.setenv("EOS_PREDICTION__LATITUDE", "37.7749")
|
||||
assert os.getenv("EOS_PREDICTION__LATITUDE") == "37.7749"
|
||||
monkeypatch.setenv("EOS_PREDICTION__LONGITUDE", "-122.4194")
|
||||
assert os.getenv("EOS_PREDICTION__LONGITUDE") == "-122.4194"
|
||||
provider.config.reset_settings()
|
||||
|
||||
ems_eos.set_start_datetime(sample_start_datetime)
|
||||
provider.update_data()
|
||||
|
||||
assert provider.config.prediction_hours == config_eos.prediction_hours
|
||||
assert provider.config.prediction_historic_hours == 2
|
||||
assert provider.config.latitude == 37.7749
|
||||
assert provider.config.longitude == -122.4194
|
||||
assert provider.config.prediction.prediction_hours == config_eos.prediction.prediction_hours
|
||||
assert provider.config.prediction.prediction_historic_hours == 2
|
||||
assert provider.config.prediction.latitude == 37.7749
|
||||
assert provider.config.prediction.longitude == -122.4194
|
||||
assert provider.start_datetime == sample_start_datetime
|
||||
assert provider.end_datetime == sample_start_datetime + to_duration(
|
||||
f"{provider.config.prediction_hours} hours"
|
||||
f"{provider.config.prediction.prediction_hours} hours"
|
||||
)
|
||||
assert provider.keep_datetime == sample_start_datetime - to_duration("2 hours")
|
||||
|
||||
def test_update_method_force_enable(self, provider, monkeypatch):
|
||||
"""Test that `update` executes when `force_enable` is True, even if `enabled` is False."""
|
||||
# Preset values that are needed by update
|
||||
monkeypatch.setenv("latitude", "37.7749")
|
||||
monkeypatch.setenv("longitude", "-122.4194")
|
||||
monkeypatch.setenv("EOS_PREDICTION__LATITUDE", "37.7749")
|
||||
monkeypatch.setenv("EOS_PREDICTION__LONGITUDE", "-122.4194")
|
||||
|
||||
# Override enabled to return False for this test
|
||||
DerivedPredictionProvider.provider_enabled = False
|
||||
@@ -288,7 +289,9 @@ class TestPredictionContainer:
|
||||
ems_eos = get_ems()
|
||||
ems_eos.set_start_datetime(to_datetime(start, in_timezone="Europe/Berlin"))
|
||||
settings = {
|
||||
"prediction_hours": hours,
|
||||
"prediction": {
|
||||
"prediction_hours": hours,
|
||||
}
|
||||
}
|
||||
container.config.merge_settings_from_dict(settings)
|
||||
expected = to_datetime(end, in_timezone="Europe/Berlin")
|
||||
@@ -316,7 +319,9 @@ class TestPredictionContainer:
|
||||
ems_eos = get_ems()
|
||||
ems_eos.set_start_datetime(to_datetime(start, in_timezone="Europe/Berlin"))
|
||||
settings = {
|
||||
"prediction_historic_hours": historic_hours,
|
||||
"prediction": {
|
||||
"prediction_historic_hours": historic_hours,
|
||||
}
|
||||
}
|
||||
container.config.merge_settings_from_dict(settings)
|
||||
expected = to_datetime(expected_keep, in_timezone="Europe/Berlin")
|
||||
@@ -336,7 +341,9 @@ class TestPredictionContainer:
|
||||
ems_eos = get_ems()
|
||||
ems_eos.set_start_datetime(to_datetime(start, in_timezone="Europe/Berlin"))
|
||||
settings = {
|
||||
"prediction_hours": prediction_hours,
|
||||
"prediction": {
|
||||
"prediction_hours": prediction_hours,
|
||||
}
|
||||
}
|
||||
container.config.merge_settings_from_dict(settings)
|
||||
assert container.total_hours == expected_hours
|
||||
@@ -355,7 +362,9 @@ class TestPredictionContainer:
|
||||
ems_eos = get_ems()
|
||||
ems_eos.set_start_datetime(to_datetime(start, in_timezone="Europe/Berlin"))
|
||||
settings = {
|
||||
"prediction_historic_hours": historic_hours,
|
||||
"prediction": {
|
||||
"prediction_historic_hours": historic_hours,
|
||||
}
|
||||
}
|
||||
container.config.merge_settings_from_dict(settings)
|
||||
assert container.keep_hours == expected_hours
|
||||
|
@@ -25,36 +25,41 @@ FILE_TESTDATA_PV_FORECAST_RESULT_1 = DIR_TESTDATA.joinpath("pv_forecast_result_1
|
||||
def sample_settings(config_eos):
|
||||
"""Fixture that adds settings data to the global config."""
|
||||
settings = {
|
||||
"prediction_hours": 48,
|
||||
"prediction_historic_hours": 24,
|
||||
"latitude": 52.52,
|
||||
"longitude": 13.405,
|
||||
"pvforecast_provider": "PVForecastAkkudoktor",
|
||||
"pvforecast0_peakpower": 5.0,
|
||||
"pvforecast0_surface_azimuth": -10,
|
||||
"pvforecast0_surface_tilt": 7,
|
||||
"pvforecast0_userhorizon": [20, 27, 22, 20],
|
||||
"pvforecast0_inverter_paco": 10000,
|
||||
"pvforecast1_peakpower": 4.8,
|
||||
"pvforecast1_surface_azimuth": -90,
|
||||
"pvforecast1_surface_tilt": 7,
|
||||
"pvforecast1_userhorizon": [30, 30, 30, 50],
|
||||
"pvforecast1_inverter_paco": 10000,
|
||||
"pvforecast2_peakpower": 1.4,
|
||||
"pvforecast2_surface_azimuth": -40,
|
||||
"pvforecast2_surface_tilt": 60,
|
||||
"pvforecast2_userhorizon": [60, 30, 0, 30],
|
||||
"pvforecast2_inverter_paco": 2000,
|
||||
"pvforecast3_peakpower": 1.6,
|
||||
"pvforecast3_surface_azimuth": 5,
|
||||
"pvforecast3_surface_tilt": 45,
|
||||
"pvforecast3_userhorizon": [45, 25, 30, 60],
|
||||
"pvforecast3_inverter_paco": 1400,
|
||||
"pvforecast4_peakpower": None,
|
||||
"prediction": {
|
||||
"prediction_hours": 48,
|
||||
"prediction_historic_hours": 24,
|
||||
"latitude": 52.52,
|
||||
"longitude": 13.405,
|
||||
},
|
||||
"pvforecast": {
|
||||
"pvforecast_provider": "PVForecastAkkudoktor",
|
||||
"pvforecast0_peakpower": 5.0,
|
||||
"pvforecast0_surface_azimuth": -10,
|
||||
"pvforecast0_surface_tilt": 7,
|
||||
"pvforecast0_userhorizon": [20, 27, 22, 20],
|
||||
"pvforecast0_inverter_paco": 10000,
|
||||
"pvforecast1_peakpower": 4.8,
|
||||
"pvforecast1_surface_azimuth": -90,
|
||||
"pvforecast1_surface_tilt": 7,
|
||||
"pvforecast1_userhorizon": [30, 30, 30, 50],
|
||||
"pvforecast1_inverter_paco": 10000,
|
||||
"pvforecast2_peakpower": 1.4,
|
||||
"pvforecast2_surface_azimuth": -40,
|
||||
"pvforecast2_surface_tilt": 60,
|
||||
"pvforecast2_userhorizon": [60, 30, 0, 30],
|
||||
"pvforecast2_inverter_paco": 2000,
|
||||
"pvforecast3_peakpower": 1.6,
|
||||
"pvforecast3_surface_azimuth": 5,
|
||||
"pvforecast3_surface_tilt": 45,
|
||||
"pvforecast3_userhorizon": [45, 25, 30, 60],
|
||||
"pvforecast3_inverter_paco": 1400,
|
||||
"pvforecast4_peakpower": None,
|
||||
},
|
||||
}
|
||||
|
||||
# Merge settings to config
|
||||
config_eos.merge_settings_from_dict(settings)
|
||||
assert config_eos.pvforecast.pvforecast_provider == "PVForecastAkkudoktor"
|
||||
return config_eos
|
||||
|
||||
|
||||
@@ -141,15 +146,19 @@ sample_value = AkkudoktorForecastValue(
|
||||
windspeed_10m=10.0,
|
||||
)
|
||||
sample_config_data = {
|
||||
"prediction_hours": 48,
|
||||
"prediction_historic_hours": 24,
|
||||
"latitude": 52.52,
|
||||
"longitude": 13.405,
|
||||
"pvforecast_provider": "PVForecastAkkudoktor",
|
||||
"pvforecast0_peakpower": 5.0,
|
||||
"pvforecast0_surface_azimuth": 180,
|
||||
"pvforecast0_surface_tilt": 30,
|
||||
"pvforecast0_inverter_paco": 10000,
|
||||
"prediction": {
|
||||
"prediction_hours": 48,
|
||||
"prediction_historic_hours": 24,
|
||||
"latitude": 52.52,
|
||||
"longitude": 13.405,
|
||||
},
|
||||
"pvforecast": {
|
||||
"pvforecast_provider": "PVForecastAkkudoktor",
|
||||
"pvforecast0_peakpower": 5.0,
|
||||
"pvforecast0_surface_azimuth": 180,
|
||||
"pvforecast0_surface_tilt": 30,
|
||||
"pvforecast0_inverter_paco": 10000,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
|
@@ -16,9 +16,13 @@ FILE_TESTDATA_PVFORECASTIMPORT_1_JSON = DIR_TESTDATA.joinpath("import_input_1.js
|
||||
def pvforecast_provider(sample_import_1_json, config_eos):
|
||||
"""Fixture to create a PVForecastProvider instance."""
|
||||
settings = {
|
||||
"pvforecast_provider": "PVForecastImport",
|
||||
"pvforecastimport_file_path": str(FILE_TESTDATA_PVFORECASTIMPORT_1_JSON),
|
||||
"pvforecastimport_json": json.dumps(sample_import_1_json),
|
||||
"pvforecast": {
|
||||
"pvforecast_provider": "PVForecastImport",
|
||||
"provider_settings": {
|
||||
"pvforecastimport_file_path": str(FILE_TESTDATA_PVFORECASTIMPORT_1_JSON),
|
||||
"pvforecastimport_json": json.dumps(sample_import_1_json),
|
||||
},
|
||||
}
|
||||
}
|
||||
config_eos.merge_settings_from_dict(settings)
|
||||
provider = PVForecastImport()
|
||||
@@ -48,8 +52,12 @@ def test_singleton_instance(pvforecast_provider):
|
||||
def test_invalid_provider(pvforecast_provider, config_eos):
|
||||
"""Test requesting an unsupported pvforecast_provider."""
|
||||
settings = {
|
||||
"pvforecast_provider": "<invalid>",
|
||||
"pvforecastimport_file_path": str(FILE_TESTDATA_PVFORECASTIMPORT_1_JSON),
|
||||
"pvforecast": {
|
||||
"pvforecast_provider": "<invalid>",
|
||||
"provider_settings": {
|
||||
"pvforecastimport_file_path": str(FILE_TESTDATA_PVFORECASTIMPORT_1_JSON),
|
||||
},
|
||||
}
|
||||
}
|
||||
config_eos.merge_settings_from_dict(settings)
|
||||
assert not pvforecast_provider.enabled()
|
||||
@@ -78,11 +86,11 @@ def test_import(pvforecast_provider, sample_import_1_json, start_datetime, from_
|
||||
ems_eos = get_ems()
|
||||
ems_eos.set_start_datetime(to_datetime(start_datetime, in_timezone="Europe/Berlin"))
|
||||
if from_file:
|
||||
config_eos.pvforecastimport_json = None
|
||||
assert config_eos.pvforecastimport_json is None
|
||||
config_eos.pvforecast.provider_settings.pvforecastimport_json = None
|
||||
assert config_eos.pvforecast.provider_settings.pvforecastimport_json is None
|
||||
else:
|
||||
config_eos.pvforecastimport_file_path = None
|
||||
assert config_eos.pvforecastimport_file_path is None
|
||||
config_eos.pvforecast.provider_settings.pvforecastimport_file_path = None
|
||||
assert config_eos.pvforecast.provider_settings.pvforecastimport_file_path is None
|
||||
pvforecast_provider.clear()
|
||||
|
||||
# Call the method
|
||||
|
@@ -6,8 +6,8 @@ import requests
|
||||
def test_server(server, config_eos):
|
||||
"""Test the server."""
|
||||
# validate correct path in server
|
||||
assert config_eos.data_folder_path is not None
|
||||
assert config_eos.data_folder_path.is_dir()
|
||||
assert config_eos.general.data_folder_path is not None
|
||||
assert config_eos.general.data_folder_path.is_dir()
|
||||
|
||||
result = requests.get(f"{server}/v1/config")
|
||||
assert result.status_code == HTTPStatus.OK
|
||||
|
@@ -13,7 +13,7 @@ reference_file = DIR_TESTDATA / "test_example_report.pdf"
|
||||
|
||||
def test_generate_pdf_example(config_eos):
|
||||
"""Test generation of example visualization report."""
|
||||
output_dir = config_eos.data_output_path
|
||||
output_dir = config_eos.general.data_output_path
|
||||
assert output_dir is not None
|
||||
output_file = output_dir / filename
|
||||
assert not output_file.exists()
|
||||
|
@@ -19,9 +19,9 @@ FILE_TESTDATA_WEATHERBRIGHTSKY_2_JSON = DIR_TESTDATA.joinpath("weatherforecast_b
|
||||
@pytest.fixture
|
||||
def weather_provider(monkeypatch):
|
||||
"""Fixture to create a WeatherProvider instance."""
|
||||
monkeypatch.setenv("weather_provider", "BrightSky")
|
||||
monkeypatch.setenv("latitude", "50.0")
|
||||
monkeypatch.setenv("longitude", "10.0")
|
||||
monkeypatch.setenv("EOS_WEATHER__WEATHER_PROVIDER", "BrightSky")
|
||||
monkeypatch.setenv("EOS_PREDICTION__LATITUDE", "50.0")
|
||||
monkeypatch.setenv("EOS_PREDICTION__LONGITUDE", "10.0")
|
||||
return WeatherBrightSky()
|
||||
|
||||
|
||||
@@ -60,19 +60,19 @@ def test_singleton_instance(weather_provider):
|
||||
|
||||
def test_invalid_provider(weather_provider, monkeypatch):
|
||||
"""Test requesting an unsupported weather_provider."""
|
||||
monkeypatch.setenv("weather_provider", "<invalid>")
|
||||
weather_provider.config.update()
|
||||
monkeypatch.setenv("EOS_WEATHER__WEATHER_PROVIDER", "<invalid>")
|
||||
weather_provider.config.reset_settings()
|
||||
assert not weather_provider.enabled()
|
||||
|
||||
|
||||
def test_invalid_coordinates(weather_provider, monkeypatch):
|
||||
"""Test invalid coordinates raise ValueError."""
|
||||
monkeypatch.setenv("latitude", "1000")
|
||||
monkeypatch.setenv("longitude", "1000")
|
||||
monkeypatch.setenv("EOS_PREDICTION__LATITUDE", "1000")
|
||||
monkeypatch.setenv("EOS_PREDICTION__LONGITUDE", "1000")
|
||||
with pytest.raises(
|
||||
ValueError, # match="Latitude '1000' and/ or longitude `1000` out of valid range."
|
||||
):
|
||||
weather_provider.config.update()
|
||||
weather_provider.config.reset_settings()
|
||||
|
||||
|
||||
# ------------------------------------------------
|
||||
|
@@ -24,9 +24,13 @@ FILE_TESTDATA_WEATHERCLEAROUTSIDE_1_DATA = DIR_TESTDATA.joinpath("weatherforecas
|
||||
def weather_provider(config_eos):
|
||||
"""Fixture to create a WeatherProvider instance."""
|
||||
settings = {
|
||||
"weather_provider": "ClearOutside",
|
||||
"latitude": 50.0,
|
||||
"longitude": 10.0,
|
||||
"weather": {
|
||||
"weather_provider": "ClearOutside",
|
||||
},
|
||||
"prediction": {
|
||||
"latitude": 50.0,
|
||||
"longitude": 10.0,
|
||||
},
|
||||
}
|
||||
config_eos.merge_settings_from_dict(settings)
|
||||
return WeatherClearOutside()
|
||||
@@ -69,7 +73,9 @@ def test_singleton_instance(weather_provider):
|
||||
def test_invalid_provider(weather_provider, config_eos):
|
||||
"""Test requesting an unsupported weather_provider."""
|
||||
settings = {
|
||||
"weather_provider": "<invalid>",
|
||||
"weather": {
|
||||
"weather_provider": "<invalid>",
|
||||
}
|
||||
}
|
||||
config_eos.merge_settings_from_dict(settings)
|
||||
assert not weather_provider.enabled()
|
||||
@@ -78,9 +84,13 @@ def test_invalid_provider(weather_provider, config_eos):
|
||||
def test_invalid_coordinates(weather_provider, config_eos):
|
||||
"""Test invalid coordinates raise ValueError."""
|
||||
settings = {
|
||||
"weather_provider": "ClearOutside",
|
||||
"latitude": 1000.0,
|
||||
"longitude": 1000.0,
|
||||
"weather": {
|
||||
"weather_provider": "ClearOutside",
|
||||
},
|
||||
"prediction": {
|
||||
"latitude": 1000.0,
|
||||
"longitude": 1000.0,
|
||||
},
|
||||
}
|
||||
with pytest.raises(
|
||||
ValueError, # match="Latitude '1000' and/ or longitude `1000` out of valid range."
|
||||
@@ -150,8 +160,8 @@ def test_update_data(mock_get, weather_provider, sample_clearout_1_html, sample_
|
||||
weather_provider.update_data()
|
||||
|
||||
# Check for correct prediction time window
|
||||
assert weather_provider.config.prediction_hours == 48
|
||||
assert weather_provider.config.prediction_historic_hours == 48
|
||||
assert weather_provider.config.prediction.prediction_hours == 48
|
||||
assert weather_provider.config.prediction.prediction_historic_hours == 48
|
||||
assert compare_datetimes(weather_provider.start_datetime, expected_start).equal
|
||||
assert compare_datetimes(weather_provider.end_datetime, expected_end).equal
|
||||
assert compare_datetimes(weather_provider.keep_datetime, expected_keep).equal
|
||||
|
@@ -16,9 +16,13 @@ FILE_TESTDATA_WEATHERIMPORT_1_JSON = DIR_TESTDATA.joinpath("import_input_1.json"
|
||||
def weather_provider(sample_import_1_json, config_eos):
|
||||
"""Fixture to create a WeatherProvider instance."""
|
||||
settings = {
|
||||
"weather_provider": "WeatherImport",
|
||||
"weatherimport_file_path": str(FILE_TESTDATA_WEATHERIMPORT_1_JSON),
|
||||
"weatherimport_json": json.dumps(sample_import_1_json),
|
||||
"weather": {
|
||||
"weather_provider": "WeatherImport",
|
||||
"provider_settings": {
|
||||
"weatherimport_file_path": str(FILE_TESTDATA_WEATHERIMPORT_1_JSON),
|
||||
"weatherimport_json": json.dumps(sample_import_1_json),
|
||||
},
|
||||
}
|
||||
}
|
||||
config_eos.merge_settings_from_dict(settings)
|
||||
provider = WeatherImport()
|
||||
@@ -48,8 +52,12 @@ def test_singleton_instance(weather_provider):
|
||||
def test_invalid_provider(weather_provider, config_eos, monkeypatch):
|
||||
"""Test requesting an unsupported weather_provider."""
|
||||
settings = {
|
||||
"weather_provider": "<invalid>",
|
||||
"weatherimport_file_path": str(FILE_TESTDATA_WEATHERIMPORT_1_JSON),
|
||||
"weather": {
|
||||
"weather_provider": "<invalid>",
|
||||
"provider_settings": {
|
||||
"weatherimport_file_path": str(FILE_TESTDATA_WEATHERIMPORT_1_JSON),
|
||||
},
|
||||
}
|
||||
}
|
||||
config_eos.merge_settings_from_dict(settings)
|
||||
assert weather_provider.enabled() == False
|
||||
@@ -78,11 +86,11 @@ def test_import(weather_provider, sample_import_1_json, start_datetime, from_fil
|
||||
ems_eos = get_ems()
|
||||
ems_eos.set_start_datetime(to_datetime(start_datetime, in_timezone="Europe/Berlin"))
|
||||
if from_file:
|
||||
config_eos.weatherimport_json = None
|
||||
assert config_eos.weatherimport_json is None
|
||||
config_eos.weather.provider_settings.weatherimport_json = None
|
||||
assert config_eos.weather.provider_settings.weatherimport_json is None
|
||||
else:
|
||||
config_eos.weatherimport_file_path = None
|
||||
assert config_eos.weatherimport_file_path is None
|
||||
config_eos.weather.provider_settings.weatherimport_file_path = None
|
||||
assert config_eos.weather.provider_settings.weatherimport_file_path is None
|
||||
weather_provider.clear()
|
||||
|
||||
# Call the method
|
||||
|
110
tests/testdata/EOS.config.json
vendored
110
tests/testdata/EOS.config.json
vendored
@@ -1,110 +0,0 @@
|
||||
{
|
||||
"config_file_path": null,
|
||||
"config_folder_path": null,
|
||||
"data_cache_path": null,
|
||||
"data_cache_subpath": null,
|
||||
"data_folder_path": null,
|
||||
"data_output_path": null,
|
||||
"data_output_subpath": null,
|
||||
"elecprice_provider": null,
|
||||
"elecpriceimport_file_path": null,
|
||||
"latitude": null,
|
||||
"load_import_file_path": null,
|
||||
"load_name": null,
|
||||
"load_provider": null,
|
||||
"loadakkudoktor_year_energy": null,
|
||||
"longitude": null,
|
||||
"optimization_ev_available_charge_rates_percent": [],
|
||||
"optimization_hours": 24,
|
||||
"optimization_penalty": null,
|
||||
"prediction_historic_hours": 48,
|
||||
"prediction_hours": 48,
|
||||
"pvforecast0_albedo": null,
|
||||
"pvforecast0_inverter_model": null,
|
||||
"pvforecast0_inverter_paco": null,
|
||||
"pvforecast0_loss": null,
|
||||
"pvforecast0_module_model": null,
|
||||
"pvforecast0_modules_per_string": null,
|
||||
"pvforecast0_mountingplace": "free",
|
||||
"pvforecast0_optimal_surface_tilt": false,
|
||||
"pvforecast0_optimalangles": false,
|
||||
"pvforecast0_peakpower": null,
|
||||
"pvforecast0_pvtechchoice": "crystSi",
|
||||
"pvforecast0_strings_per_inverter": null,
|
||||
"pvforecast0_surface_azimuth": null,
|
||||
"pvforecast0_surface_tilt": null,
|
||||
"pvforecast0_trackingtype": null,
|
||||
"pvforecast0_userhorizon": null,
|
||||
"pvforecast1_albedo": null,
|
||||
"pvforecast1_inverter_model": null,
|
||||
"pvforecast1_inverter_paco": null,
|
||||
"pvforecast1_loss": 0,
|
||||
"pvforecast1_module_model": null,
|
||||
"pvforecast1_modules_per_string": null,
|
||||
"pvforecast1_mountingplace": "free",
|
||||
"pvforecast1_optimal_surface_tilt": false,
|
||||
"pvforecast1_optimalangles": false,
|
||||
"pvforecast1_peakpower": null,
|
||||
"pvforecast1_pvtechchoice": "crystSi",
|
||||
"pvforecast1_strings_per_inverter": null,
|
||||
"pvforecast1_surface_azimuth": null,
|
||||
"pvforecast1_surface_tilt": null,
|
||||
"pvforecast1_trackingtype": null,
|
||||
"pvforecast1_userhorizon": null,
|
||||
"pvforecast2_albedo": null,
|
||||
"pvforecast2_inverter_model": null,
|
||||
"pvforecast2_inverter_paco": null,
|
||||
"pvforecast2_loss": 0,
|
||||
"pvforecast2_module_model": null,
|
||||
"pvforecast2_modules_per_string": null,
|
||||
"pvforecast2_mountingplace": "free",
|
||||
"pvforecast2_optimal_surface_tilt": false,
|
||||
"pvforecast2_optimalangles": false,
|
||||
"pvforecast2_peakpower": null,
|
||||
"pvforecast2_pvtechchoice": "crystSi",
|
||||
"pvforecast2_strings_per_inverter": null,
|
||||
"pvforecast2_surface_azimuth": null,
|
||||
"pvforecast2_surface_tilt": null,
|
||||
"pvforecast2_trackingtype": null,
|
||||
"pvforecast2_userhorizon": null,
|
||||
"pvforecast3_albedo": null,
|
||||
"pvforecast3_inverter_model": null,
|
||||
"pvforecast3_inverter_paco": null,
|
||||
"pvforecast3_loss": 0,
|
||||
"pvforecast3_module_model": null,
|
||||
"pvforecast3_modules_per_string": null,
|
||||
"pvforecast3_mountingplace": "free",
|
||||
"pvforecast3_optimal_surface_tilt": false,
|
||||
"pvforecast3_optimalangles": false,
|
||||
"pvforecast3_peakpower": null,
|
||||
"pvforecast3_pvtechchoice": "crystSi",
|
||||
"pvforecast3_strings_per_inverter": null,
|
||||
"pvforecast3_surface_azimuth": null,
|
||||
"pvforecast3_surface_tilt": null,
|
||||
"pvforecast3_trackingtype": null,
|
||||
"pvforecast3_userhorizon": null,
|
||||
"pvforecast4_albedo": null,
|
||||
"pvforecast4_inverter_model": null,
|
||||
"pvforecast4_inverter_paco": null,
|
||||
"pvforecast4_loss": 0,
|
||||
"pvforecast4_module_model": null,
|
||||
"pvforecast4_modules_per_string": null,
|
||||
"pvforecast4_mountingplace": "free",
|
||||
"pvforecast4_optimal_surface_tilt": false,
|
||||
"pvforecast4_optimalangles": false,
|
||||
"pvforecast4_peakpower": null,
|
||||
"pvforecast4_pvtechchoice": "crystSi",
|
||||
"pvforecast4_strings_per_inverter": null,
|
||||
"pvforecast4_surface_azimuth": null,
|
||||
"pvforecast4_surface_tilt": null,
|
||||
"pvforecast4_trackingtype": null,
|
||||
"pvforecast4_userhorizon": null,
|
||||
"pvforecast_provider": null,
|
||||
"pvforecastimport_file_path": null,
|
||||
"server_eos_host": "0.0.0.0",
|
||||
"server_eos_port": 8503,
|
||||
"server_eosdash_host": "0.0.0.0",
|
||||
"server_eosdash_port": 8504,
|
||||
"weather_provider": null,
|
||||
"weatherimport_file_path": null
|
||||
}
|
6
tests/testdata/optimize_input_1.json
vendored
6
tests/testdata/optimize_input_1.json
vendored
@@ -26,15 +26,19 @@
|
||||
]
|
||||
},
|
||||
"pv_akku": {
|
||||
"device_id": "battery1",
|
||||
"capacity_wh": 26400,
|
||||
"max_charge_power_w": 5000,
|
||||
"initial_soc_percentage": 80,
|
||||
"min_soc_percentage": 15
|
||||
},
|
||||
"inverter": {
|
||||
"max_power_wh": 10000
|
||||
"device_id": "inverter1",
|
||||
"max_power_wh": 10000,
|
||||
"battery": "battery1"
|
||||
},
|
||||
"eauto": {
|
||||
"device_id": "ev1",
|
||||
"capacity_wh": 60000,
|
||||
"charging_efficiency": 0.95,
|
||||
"discharging_efficiency": 1.0,
|
||||
|
8
tests/testdata/optimize_input_2.json
vendored
8
tests/testdata/optimize_input_2.json
vendored
@@ -154,6 +154,7 @@
|
||||
]
|
||||
},
|
||||
"pv_akku": {
|
||||
"device_id": "battery1",
|
||||
"capacity_wh": 26400,
|
||||
"initial_soc_percentage": 80,
|
||||
"min_soc_percentage": 0
|
||||
@@ -162,13 +163,20 @@
|
||||
"max_power_wh": 10000
|
||||
},
|
||||
"eauto": {
|
||||
"device_id": "ev1",
|
||||
"capacity_wh": 60000,
|
||||
"charging_efficiency": 0.95,
|
||||
"max_charge_power_w": 11040,
|
||||
"initial_soc_percentage": 5,
|
||||
"min_soc_percentage": 80
|
||||
},
|
||||
"inverter": {
|
||||
"device_id": "inverter1",
|
||||
"max_power_wh": 10000,
|
||||
"battery": "battery1"
|
||||
},
|
||||
"dishwasher": {
|
||||
"device_id": "dishwasher1",
|
||||
"consumption_wh": 5000,
|
||||
"duration_h": 2
|
||||
},
|
||||
|
1
tests/testdata/optimize_result_1.json
vendored
1
tests/testdata/optimize_result_1.json
vendored
@@ -557,6 +557,7 @@
|
||||
]
|
||||
},
|
||||
"eauto_obj": {
|
||||
"device_id": "ev1",
|
||||
"charge_array": [
|
||||
1.0,
|
||||
1.0,
|
||||
|
1
tests/testdata/optimize_result_2.json
vendored
1
tests/testdata/optimize_result_2.json
vendored
@@ -606,6 +606,7 @@
|
||||
]
|
||||
},
|
||||
"eauto_obj": {
|
||||
"device_id": "ev1",
|
||||
"charge_array": [
|
||||
1.0,
|
||||
1.0,
|
||||
|
1
tests/testdata/optimize_result_2_full.json
vendored
1
tests/testdata/optimize_result_2_full.json
vendored
@@ -606,6 +606,7 @@
|
||||
]
|
||||
},
|
||||
"eauto_obj": {
|
||||
"device_id": "ev1",
|
||||
"charge_array": [
|
||||
1.0,
|
||||
1.0,
|
||||
|
Reference in New Issue
Block a user