fix: prevent exception when load prediction data is missing (#925)
Some checks failed
Bump Version / Bump Version Workflow (push) Has been cancelled
docker-build / platform-excludes (push) Has been cancelled
pre-commit / pre-commit (push) Has been cancelled
Run Pytest on Pull Request / test (push) Has been cancelled
docker-build / build (push) Has been cancelled
docker-build / merge (push) Has been cancelled
Close stale pull requests/issues / Find Stale issues and PRs (push) Has been cancelled

Validate solution prediction data before processing.
If required prediction data is missing, the prediction is skipped
instead of raising an exception.

Introduce a new configuration file saving policy to improve loading robustness:
- Exclude computed fields
- Exclude fields set to their default values
- Exclude fields with value None
- Use field aliases
- Recursively remove empty dictionaries and lists
- Ensure general.version is always present and correctly set

When loading older configuration files, computed fields are now stripped
before migration. This further improves backward compatibility and loading
robustness.

Signed-off-by: Bobby Noelte <b0661n0e17e@gmail.com>
This commit is contained in:
Bobby Noelte
2026-03-07 14:46:30 +01:00
committed by GitHub
parent 36e2e4c15b
commit 997e7646e9
21 changed files with 1282 additions and 299 deletions

99
tests/test_configfile.py Normal file
View File

@@ -0,0 +1,99 @@
import json
from pathlib import Path
from unittest.mock import patch
import pytest
from akkudoktoreos.config.config import ConfigEOS, GeneralSettings
class TestConfigEOSToConfigFile:
def test_to_config_file_writes_file(self, config_eos):
config_path = config_eos.general.config_file_path
# Remove file to test writing
config_path.unlink(missing_ok=True)
config_eos.to_config_file()
assert config_path.exists()
assert config_path.read_text().strip().startswith("{")
def test_to_config_file_excludes_computed_fields(self, config_eos):
config_path = config_eos.general.config_file_path
config_eos.to_config_file()
data = json.loads(config_path.read_text())
assert "timezone" not in data["general"]
assert "data_output_path" not in data["general"]
assert "config_folder_path" not in data["general"]
assert "config_file_path" not in data["general"]
def test_to_config_file_excludes_defaults(self, config_eos):
"""Ensure fields with default values are excluded when saving config."""
# Pick fields that have defaults
default_latitude = GeneralSettings.model_fields["latitude"].default
default_longitude = GeneralSettings.model_fields["longitude"].default
# Ensure fields are at default values
config_eos.general.latitude = default_latitude
config_eos.general.longitude = default_longitude
# Save the config using the correct path managed by config_eos
config_eos.to_config_file()
# Read back JSON from the correct path
config_file_path = config_eos.general.config_file_path
content = json.loads(config_file_path.read_text(encoding="utf-8"))
# Default fields should not appear
assert "latitude" not in content["general"]
assert "longitude" not in content["general"]
# Non-default value should appear
config_eos.general.latitude = 48.0
config_eos.to_config_file()
content = json.loads(config_file_path.read_text(encoding="utf-8"))
assert content["general"]["latitude"] == 48.0
def test_to_config_file_excludes_none_fields(self, config_eos):
config_eos.general.latitude = None
config_path = config_eos.general.config_file_path
config_eos.to_config_file()
data = json.loads(config_path.read_text())
assert "latitude" not in data["general"]
def test_to_config_file_includes_version(tmp_path, config_eos):
"""Ensure general.version is always included."""
# Save config
config_eos.to_config_file()
# Read back JSON
config_file_path = config_eos.general.config_file_path
content = json.loads(config_file_path.read_text(encoding="utf-8"))
# Assert 'version' is included even if default
assert content["general"]["version"] == config_eos.general.version
def test_to_config_file_roundtrip(self, config_eos):
config_eos.merge_settings_from_dict(
{
"general": {"latitude": 48.0},
"server": {"port": 9000},
}
)
config_path = config_eos.general.config_file_path
config_eos.to_config_file()
raw_data = json.loads(config_path.read_text())
reloaded = ConfigEOS.model_validate(raw_data)
assert reloaded.general.latitude == 48.0
assert reloaded.server.port == 9000

View File

@@ -22,6 +22,10 @@ MIGRATION_PAIRS = [
DIR_TESTDATA / "eos_config_andreas_0_1_0.json",
DIR_TESTDATA / "eos_config_andreas_now.json",
),
(
DIR_TESTDATA / "eos_config_unstripped.json",
DIR_TESTDATA / "eos_config_stripped.json",
),
# Add more pairs here:
# (DIR_TESTDATA / "old_config_X.json", DIR_TESTDATA / "expected_config_X.json"),
]
@@ -124,16 +128,18 @@ class TestConfigMigration:
new_model = SettingsEOSDefaults(**migrated_data)
assert isinstance(new_model, SettingsEOSDefaults)
def test_migrate_config_file_already_current(self, tmp_path: Path):
def test_migrate_config_file_already_current(self, tmp_config_file: Path):
"""Test that a current config file returns True immediately."""
config_path = tmp_path / "EOS_current.json"
default = SettingsEOSDefaults()
with config_path.open("w", encoding="utf-8") as f:
f.write(default.model_dump_json(indent=4))
backup_file = tmp_config_file.with_suffix(".bak")
backup_file = config_path.with_suffix(".bak")
# Run migration
result = configmigrate.migrate_config_file(tmp_config_file, backup_file)
assert result is True, "Migration should succeed even from invalid version."
result = configmigrate.migrate_config_file(config_path, backup_file)
backup_file.unlink()
assert not backup_file.exists()
result = configmigrate.migrate_config_file(tmp_config_file, backup_file)
assert result is True
assert not backup_file.exists(), "No backup should be made if config is already current."

View File

@@ -1,98 +1,83 @@
{
"general": {
"data_folder_path": "__ANY__",
"data_output_subpath": "output",
"latitude": 52.5,
"longitude": 13.4
},
"cache": {
"subpath": "cache",
"cleanup_interval": 300.0
},
"ems": {
"startup_delay": 5.0,
"interval": 300.0
},
"logging": {
"console_level": "INFO"
},
"devices": {
"batteries": [
{
"device_id": "pv_akku",
"capacity_wh": 30000,
"charging_efficiency": 0.88,
"discharging_efficiency": 0.88,
"max_charge_power_w": 5000,
"min_soc_percentage": 0,
"max_soc_percentage": 100
}
],
"electric_vehicles": [
{
"charge_rates": [0.0, 0.375, 0.5, 0.625, 0.75, 0.875, 1.0]
}
],
"inverters": [],
"home_appliances": []
},
"measurement": {
"load_emr_keys": ["Household"]
},
"optimization": {
"horizon_hours": 48,
"genetic": {
"penalties": {
"ev_soc_miss": 10
}
"general": {
"version": "__ANY__",
"data_output_subpath": "output",
"latitude": 52.5,
"longitude": 13.4
},
"cache": {
"subpath": "cache"
},
"logging": {
"console_level": "INFO"
},
"devices": {
"batteries": [
{
"device_id": "pv_akku",
"capacity_wh": 30000
}
],
"electric_vehicles": [
{
"charge_rates": [
0.0,
0.375,
0.5,
0.625,
0.75,
0.875,
1.0
]
}
]
},
"measurement": {
"load_emr_keys": [
"Household"
]
},
"optimization": {
"horizon_hours": 48
},
"elecprice": {
"provider": "ElecPriceAkkudoktor",
"charges_kwh": 0.21
},
"load": {
"loadakkudoktor": {
"loadakkudoktor_year_energy_kwh": 13000.0
}
},
"pvforecast": {
"provider": "PVForecastAkkudoktor",
"planes": [
{
"surface_tilt": 87.907,
"surface_azimuth": 175.0,
"userhorizon": [
28.0,
34.0,
32.0,
60.0
],
"peakpower": 13.11,
"loss": 18.6,
"trackingtype": 0,
"albedo": 0.25,
"inverter_paco": 15000,
"modules_per_string": 20,
"strings_per_inverter": 2
}
]
},
"weather": {
"provider": "WeatherImport"
},
"server": {
"host": "0.0.0.0",
"verbose": true,
"eosdash_host": "0.0.0.0",
"eosdash_port": 8504
}
},
"prediction": {
"hours": 48,
"historic_hours": 48
},
"elecprice": {
"provider": "ElecPriceAkkudoktor",
"charges_kwh": 0.21
},
"load": {
"loadakkudoktor": {
"loadakkudoktor_year_energy_kwh": 13000
}
},
"pvforecast": {
"provider": "PVForecastAkkudoktor",
"planes": [
{
"surface_tilt": 87.907,
"surface_azimuth": 175.0,
"userhorizon": [28.0, 34.0, 32.0, 60.0],
"peakpower": 13.110,
"pvtechchoice": "crystSi",
"mountingplace": "free",
"loss": 18.6,
"trackingtype": 0,
"optimal_surface_tilt": false,
"optimalangles": false,
"albedo": 0.25,
"module_model": null,
"inverter_model": null,
"inverter_paco": 15000,
"modules_per_string": 20,
"strings_per_inverter": 2
}
]
},
"weather": {
"provider": "WeatherImport"
},
"server": {
"host": "0.0.0.0",
"port": 8503,
"verbose": true,
"startup_eosdash": true,
"eosdash_host": "0.0.0.0",
"eosdash_port": 8504
},
"utils": {}
}

View File

@@ -1,29 +1,20 @@
{
"elecprice": {
"charges_kwh": 0.21,
"provider": "ElecPriceImport"
},
"prediction": {
"historic_hours": 48,
"hours": 48
},
"optimization": {
"horizon_hours": 48,
"algorithm": "GENETIC",
"genetic": {
"individuals": 300,
"generations": 400
"general": {
"version": "__ANY__",
"data_output_subpath": "output",
"latitude": 52.5,
"longitude": 13.4
},
"optimization": {
"horizon_hours": 48
},
"elecprice": {
"provider": "ElecPriceImport",
"charges_kwh": 0.21
},
"server": {
"host": "0.0.0.0",
"eosdash_host": "0.0.0.0",
"eosdash_port": 8504
}
},
"general": {
"latitude": 52.5,
"longitude": 13.4
},
"server": {
"startup_eosdash": true,
"host": "0.0.0.0",
"port": 8503,
"eosdash_host": "0.0.0.0",
"eosdash_port": 8504
}
}
}

134
tests/testdata/eos_config_stripped.json vendored Normal file
View File

@@ -0,0 +1,134 @@
{
"general": {
"version": "__ANY__",
"data_output_subpath": "output"
},
"cache": {
"subpath": "cache"
},
"ems": {
"mode": "OPTIMIZATION"
},
"devices": {
"batteries": [
{
"device_id": "battery1"
}
],
"max_batteries": 1,
"electric_vehicles": [
{
"device_id": "ev11",
"capacity_wh": 50000,
"min_soc_percentage": 70
}
],
"max_electric_vehicles": 1,
"inverters": [
{
"device_id": "inverter1",
"max_power_w": 10000.0,
"battery_id": "battery1"
}
],
"max_inverters": 1,
"home_appliances": [
{
"device_id": "dishwasher1",
"consumption_wh": 2000,
"duration_h": 3,
"time_windows": {
"windows": [
{
"start_time": "08:00:00.000000 Europe/Berlin",
"duration": "5 hours"
},
{
"start_time": "15:00:00.000000 Europe/Berlin",
"duration": "3 hours"
}
]
}
}
],
"max_home_appliances": 1
},
"elecprice": {
"provider": "ElecPriceAkkudoktor"
},
"feedintariff": {
"provider": "FeedInTariffFixed",
"provider_settings": {
"FeedInTariffFixed": {
"feed_in_tariff_kwh": 0.078
}
}
},
"load": {
"provider": "LoadAkkudoktorAdjusted",
"loadakkudoktor": {
"loadakkudoktor_year_energy_kwh": 3000.0
}
},
"pvforecast": {
"provider": "PVForecastAkkudoktor",
"planes": [
{
"surface_tilt": 7.0,
"surface_azimuth": 170.0,
"userhorizon": [
20.0,
27.0,
22.0,
20.0
],
"peakpower": 5.0,
"inverter_paco": 10000
},
{
"surface_tilt": 7.0,
"surface_azimuth": 90.0,
"userhorizon": [
30.0,
30.0,
30.0,
50.0
],
"peakpower": 4.8,
"inverter_paco": 10000
},
{
"surface_tilt": 60.0,
"surface_azimuth": 140.0,
"userhorizon": [
60.0,
30.0,
0.0,
30.0
],
"peakpower": 1.4,
"inverter_paco": 2000
},
{
"surface_tilt": 45.0,
"surface_azimuth": 185.0,
"userhorizon": [
45.0,
25.0,
30.0,
60.0
],
"peakpower": 1.6,
"inverter_paco": 1400
}
],
"max_planes": 4
},
"weather": {
"provider": "BrightSky"
},
"server": {
"eosdash_host": "127.0.0.1",
"eosdash_port": 8504
}
}

View File

@@ -0,0 +1,596 @@
{
"general": {
"version": "0.2.0.dev2603031877440961",
"data_folder_path": "/home/bobby/.local/share/net.akkudoktor.eos",
"data_output_subpath": "output",
"latitude": 52.52,
"longitude": 13.405,
"timezone": "Europe/Berlin",
"data_output_path": "/home/bobby/.local/share/net.akkudoktor.eos/output",
"config_folder_path": "/home/bobby/.config/net.akkudoktor.eos",
"config_file_path": "/home/bobby/.config/net.akkudoktor.eos/EOS.config.json"
},
"cache": {
"subpath": "cache",
"cleanup_interval": 300
},
"database": {
"provider": null,
"compression_level": 9,
"initial_load_window_h": null,
"keep_duration_h": null,
"autosave_interval_sec": 10,
"compaction_interval_sec": 604800,
"batch_size": 100,
"providers": [
"LMDB",
"SQLite"
]
},
"ems": {
"startup_delay": 5,
"interval": 300,
"mode": "OPTIMIZATION"
},
"logging": {
"console_level": null,
"file_level": null,
"file_path": "/home/bobby/.local/share/net.akkudoktor.eos/output/eos.log"
},
"devices": {
"batteries": [
{
"device_id": "battery1",
"capacity_wh": 8000,
"charging_efficiency": 0.88,
"discharging_efficiency": 0.88,
"levelized_cost_of_storage_kwh": 0,
"max_charge_power_w": 5000,
"min_charge_power_w": 50,
"charge_rates": [
0,
0.1,
0.2,
0.3,
0.4,
0.5,
0.6,
0.7,
0.8,
0.9,
1
],
"min_soc_percentage": 0,
"max_soc_percentage": 100,
"measurement_key_soc_factor": "battery1-soc-factor",
"measurement_key_power_l1_w": "battery1-power-l1-w",
"measurement_key_power_l2_w": "battery1-power-l2-w",
"measurement_key_power_l3_w": "battery1-power-l3-w",
"measurement_key_power_3_phase_sym_w": "battery1-power-3-phase-sym-w",
"measurement_keys": [
"battery1-soc-factor",
"battery1-power-l1-w",
"battery1-power-l2-w",
"battery1-power-l3-w",
"battery1-power-3-phase-sym-w"
]
}
],
"max_batteries": 1,
"electric_vehicles": [
{
"device_id": "ev11",
"capacity_wh": 50000,
"charging_efficiency": 0.88,
"discharging_efficiency": 0.88,
"levelized_cost_of_storage_kwh": 0,
"max_charge_power_w": 5000,
"min_charge_power_w": 50,
"charge_rates": [
0,
0.1,
0.2,
0.3,
0.4,
0.5,
0.6,
0.7,
0.8,
0.9,
1
],
"min_soc_percentage": 70,
"max_soc_percentage": 100,
"measurement_key_soc_factor": "ev11-soc-factor",
"measurement_key_power_l1_w": "ev11-power-l1-w",
"measurement_key_power_l2_w": "ev11-power-l2-w",
"measurement_key_power_l3_w": "ev11-power-l3-w",
"measurement_key_power_3_phase_sym_w": "ev11-power-3-phase-sym-w",
"measurement_keys": [
"ev11-soc-factor",
"ev11-power-l1-w",
"ev11-power-l2-w",
"ev11-power-l3-w",
"ev11-power-3-phase-sym-w"
]
}
],
"max_electric_vehicles": 1,
"inverters": [
{
"device_id": "inverter1",
"max_power_w": 10000,
"battery_id": "battery1",
"ac_to_dc_efficiency": 1,
"dc_to_ac_efficiency": 1,
"max_ac_charge_power_w": null,
"measurement_keys": []
}
],
"max_inverters": 1,
"home_appliances": [
{
"device_id": "dishwasher1",
"consumption_wh": 2000,
"duration_h": 3,
"time_windows": {
"windows": [
{
"start_time": "08:00:00.000000 Europe/Berlin",
"duration": "5 hours",
"day_of_week": null,
"date": null,
"locale": null
},
{
"start_time": "15:00:00.000000 Europe/Berlin",
"duration": "3 hours",
"day_of_week": null,
"date": null,
"locale": null
}
]
},
"measurement_keys": []
}
],
"max_home_appliances": 1,
"measurement_keys": [
"battery1-soc-factor",
"battery1-power-l1-w",
"battery1-power-l2-w",
"battery1-power-l3-w",
"battery1-power-3-phase-sym-w",
"ev11-soc-factor",
"ev11-power-l1-w",
"ev11-power-l2-w",
"ev11-power-l3-w",
"ev11-power-3-phase-sym-w"
]
},
"measurement": {
"historic_hours": 17520,
"load_emr_keys": null,
"grid_export_emr_keys": null,
"grid_import_emr_keys": null,
"pv_production_emr_keys": null,
"keys": []
},
"optimization": {
"horizon_hours": 24,
"interval": 3600,
"algorithm": "GENETIC",
"genetic": {
"individuals": 300,
"generations": 400,
"seed": null,
"penalties": {
"ev_soc_miss": 10,
"ac_charge_break_even": 1
}
},
"keys": [
"battery1_fault_op_factor",
"battery1_fault_op_mode",
"battery1_forced_charge_op_factor",
"battery1_forced_charge_op_mode",
"battery1_forced_discharge_op_factor",
"battery1_forced_discharge_op_mode",
"battery1_frequency_regulation_op_factor",
"battery1_frequency_regulation_op_mode",
"battery1_grid_support_export_op_factor",
"battery1_grid_support_export_op_mode",
"battery1_grid_support_import_op_factor",
"battery1_grid_support_import_op_mode",
"battery1_idle_op_factor",
"battery1_idle_op_mode",
"battery1_non_export_op_factor",
"battery1_non_export_op_mode",
"battery1_outage_supply_op_factor",
"battery1_outage_supply_op_mode",
"battery1_peak_shaving_op_factor",
"battery1_peak_shaving_op_mode",
"battery1_ramp_rate_control_op_factor",
"battery1_ramp_rate_control_op_mode",
"battery1_reserve_backup_op_factor",
"battery1_reserve_backup_op_mode",
"battery1_self_consumption_op_factor",
"battery1_self_consumption_op_mode",
"battery1_soc_factor",
"costs_amt",
"date_time",
"ev11_fault_op_factor",
"ev11_fault_op_mode",
"ev11_forced_charge_op_factor",
"ev11_forced_charge_op_mode",
"ev11_forced_discharge_op_factor",
"ev11_forced_discharge_op_mode",
"ev11_frequency_regulation_op_factor",
"ev11_frequency_regulation_op_mode",
"ev11_grid_support_export_op_factor",
"ev11_grid_support_export_op_mode",
"ev11_grid_support_import_op_factor",
"ev11_grid_support_import_op_mode",
"ev11_idle_op_factor",
"ev11_idle_op_mode",
"ev11_non_export_op_factor",
"ev11_non_export_op_mode",
"ev11_outage_supply_op_factor",
"ev11_outage_supply_op_mode",
"ev11_peak_shaving_op_factor",
"ev11_peak_shaving_op_mode",
"ev11_ramp_rate_control_op_factor",
"ev11_ramp_rate_control_op_mode",
"ev11_reserve_backup_op_factor",
"ev11_reserve_backup_op_mode",
"ev11_self_consumption_op_factor",
"ev11_self_consumption_op_mode",
"ev11_soc_factor",
"genetic_ac_charge_factor",
"genetic_dc_charge_factor",
"genetic_discharge_allowed_factor",
"genetic_ev_charge_factor",
"grid_consumption_energy_wh",
"grid_feedin_energy_wh",
"homeappliance1_energy_wh",
"homeappliance1_off_op_factor",
"homeappliance1_off_op_mode",
"homeappliance1_run_op_factor",
"homeappliance1_run_op_mode",
"load_energy_wh",
"losses_energy_wh",
"revenue_amt"
]
},
"prediction": {
"hours": 48,
"historic_hours": 48
},
"elecprice": {
"provider": "ElecPriceAkkudoktor",
"charges_kwh": null,
"vat_rate": 1.19,
"elecpriceimport": {
"import_file_path": null,
"import_json": null
},
"energycharts": {
"bidding_zone": "DE-LU"
},
"providers": [
"ElecPriceAkkudoktor",
"ElecPriceEnergyCharts",
"ElecPriceImport"
]
},
"feedintariff": {
"provider": "FeedInTariffFixed",
"provider_settings": {
"FeedInTariffFixed": {
"feed_in_tariff_kwh": 0.078
},
"FeedInTariffImport": null
},
"providers": [
"FeedInTariffFixed",
"FeedInTariffImport"
]
},
"load": {
"provider": "LoadAkkudoktorAdjusted",
"loadakkudoktor": {
"loadakkudoktor_year_energy_kwh": 3000
},
"loadvrm": {
"load_vrm_token": "your-token",
"load_vrm_idsite": 12345
},
"loadimport": {
"import_file_path": null,
"import_json": null
},
"providers": [
"LoadAkkudoktor",
"LoadAkkudoktorAdjusted",
"LoadVrm",
"LoadImport"
]
},
"pvforecast": {
"provider": "PVForecastAkkudoktor",
"provider_settings": {
"PVForecastImport": null,
"PVForecastVrm": null
},
"planes": [
{
"surface_tilt": 7,
"surface_azimuth": 170,
"userhorizon": [
20,
27,
22,
20
],
"peakpower": 5,
"pvtechchoice": "crystSi",
"mountingplace": "free",
"loss": 14,
"trackingtype": null,
"optimal_surface_tilt": false,
"optimalangles": false,
"albedo": null,
"module_model": null,
"inverter_model": null,
"inverter_paco": 10000,
"modules_per_string": null,
"strings_per_inverter": null
},
{
"surface_tilt": 7,
"surface_azimuth": 90,
"userhorizon": [
30,
30,
30,
50
],
"peakpower": 4.8,
"pvtechchoice": "crystSi",
"mountingplace": "free",
"loss": 14,
"trackingtype": null,
"optimal_surface_tilt": false,
"optimalangles": false,
"albedo": null,
"module_model": null,
"inverter_model": null,
"inverter_paco": 10000,
"modules_per_string": null,
"strings_per_inverter": null
},
{
"surface_tilt": 60,
"surface_azimuth": 140,
"userhorizon": [
60,
30,
0,
30
],
"peakpower": 1.4,
"pvtechchoice": "crystSi",
"mountingplace": "free",
"loss": 14,
"trackingtype": null,
"optimal_surface_tilt": false,
"optimalangles": false,
"albedo": null,
"module_model": null,
"inverter_model": null,
"inverter_paco": 2000,
"modules_per_string": null,
"strings_per_inverter": null
},
{
"surface_tilt": 45,
"surface_azimuth": 185,
"userhorizon": [
45,
25,
30,
60
],
"peakpower": 1.6,
"pvtechchoice": "crystSi",
"mountingplace": "free",
"loss": 14,
"trackingtype": null,
"optimal_surface_tilt": false,
"optimalangles": false,
"albedo": null,
"module_model": null,
"inverter_model": null,
"inverter_paco": 1400,
"modules_per_string": null,
"strings_per_inverter": null
}
],
"max_planes": 4,
"providers": [
"PVForecastAkkudoktor",
"PVForecastVrm",
"PVForecastImport"
],
"planes_peakpower": [
5,
4.8,
1.4,
1.6
],
"planes_azimuth": [
170,
90,
140,
185
],
"planes_tilt": [
7,
7,
60,
45
],
"planes_userhorizon": [
[
20,
27,
22,
20
],
[
30,
30,
30,
50
],
[
60,
30,
0,
30
],
[
45,
25,
30,
60
]
],
"planes_inverter_paco": [
10000,
10000,
2000,
1400
]
},
"weather": {
"provider": "BrightSky",
"provider_settings": {
"WeatherImport": null
},
"providers": [
"BrightSky",
"ClearOutside",
"WeatherImport"
]
},
"server": {
"host": "127.0.0.1",
"port": 8503,
"verbose": false,
"startup_eosdash": true,
"eosdash_host": "127.0.0.1",
"eosdash_port": 8504,
"eosdash_supervise_interval_sec": 10
},
"utils": {},
"adapter": {
"provider": null,
"homeassistant": {
"config_entity_ids": null,
"load_emr_entity_ids": null,
"grid_export_emr_entity_ids": null,
"grid_import_emr_entity_ids": null,
"pv_production_emr_entity_ids": null,
"device_measurement_entity_ids": null,
"device_instruction_entity_ids": null,
"solution_entity_ids": null,
"homeassistant_entity_ids": [],
"eos_solution_entity_ids": [
"sensor.eos_battery1_fault_op_factor",
"sensor.eos_battery1_fault_op_mode",
"sensor.eos_battery1_forced_charge_op_factor",
"sensor.eos_battery1_forced_charge_op_mode",
"sensor.eos_battery1_forced_discharge_op_factor",
"sensor.eos_battery1_forced_discharge_op_mode",
"sensor.eos_battery1_frequency_regulation_op_factor",
"sensor.eos_battery1_frequency_regulation_op_mode",
"sensor.eos_battery1_grid_support_export_op_factor",
"sensor.eos_battery1_grid_support_export_op_mode",
"sensor.eos_battery1_grid_support_import_op_factor",
"sensor.eos_battery1_grid_support_import_op_mode",
"sensor.eos_battery1_idle_op_factor",
"sensor.eos_battery1_idle_op_mode",
"sensor.eos_battery1_non_export_op_factor",
"sensor.eos_battery1_non_export_op_mode",
"sensor.eos_battery1_outage_supply_op_factor",
"sensor.eos_battery1_outage_supply_op_mode",
"sensor.eos_battery1_peak_shaving_op_factor",
"sensor.eos_battery1_peak_shaving_op_mode",
"sensor.eos_battery1_ramp_rate_control_op_factor",
"sensor.eos_battery1_ramp_rate_control_op_mode",
"sensor.eos_battery1_reserve_backup_op_factor",
"sensor.eos_battery1_reserve_backup_op_mode",
"sensor.eos_battery1_self_consumption_op_factor",
"sensor.eos_battery1_self_consumption_op_mode",
"sensor.eos_battery1_soc_factor",
"sensor.eos_costs_amt",
"sensor.eos_date_time",
"sensor.eos_ev11_fault_op_factor",
"sensor.eos_ev11_fault_op_mode",
"sensor.eos_ev11_forced_charge_op_factor",
"sensor.eos_ev11_forced_charge_op_mode",
"sensor.eos_ev11_forced_discharge_op_factor",
"sensor.eos_ev11_forced_discharge_op_mode",
"sensor.eos_ev11_frequency_regulation_op_factor",
"sensor.eos_ev11_frequency_regulation_op_mode",
"sensor.eos_ev11_grid_support_export_op_factor",
"sensor.eos_ev11_grid_support_export_op_mode",
"sensor.eos_ev11_grid_support_import_op_factor",
"sensor.eos_ev11_grid_support_import_op_mode",
"sensor.eos_ev11_idle_op_factor",
"sensor.eos_ev11_idle_op_mode",
"sensor.eos_ev11_non_export_op_factor",
"sensor.eos_ev11_non_export_op_mode",
"sensor.eos_ev11_outage_supply_op_factor",
"sensor.eos_ev11_outage_supply_op_mode",
"sensor.eos_ev11_peak_shaving_op_factor",
"sensor.eos_ev11_peak_shaving_op_mode",
"sensor.eos_ev11_ramp_rate_control_op_factor",
"sensor.eos_ev11_ramp_rate_control_op_mode",
"sensor.eos_ev11_reserve_backup_op_factor",
"sensor.eos_ev11_reserve_backup_op_mode",
"sensor.eos_ev11_self_consumption_op_factor",
"sensor.eos_ev11_self_consumption_op_mode",
"sensor.eos_ev11_soc_factor",
"sensor.eos_genetic_ac_charge_factor",
"sensor.eos_genetic_dc_charge_factor",
"sensor.eos_genetic_discharge_allowed_factor",
"sensor.eos_genetic_ev_charge_factor",
"sensor.eos_grid_consumption_energy_wh",
"sensor.eos_grid_feedin_energy_wh",
"sensor.eos_homeappliance1_energy_wh",
"sensor.eos_homeappliance1_off_op_factor",
"sensor.eos_homeappliance1_off_op_mode",
"sensor.eos_homeappliance1_run_op_factor",
"sensor.eos_homeappliance1_run_op_mode",
"sensor.eos_load_energy_wh",
"sensor.eos_losses_energy_wh",
"sensor.eos_revenue_amt"
],
"eos_device_instruction_entity_ids": [
"sensor.eos_battery1",
"sensor.eos_ev11",
"sensor.eos_homeappliance1"
]
},
"nodered": {
"host": "127.0.0.1",
"port": 1880
},
"providers": [
"HomeAssistant",
"NodeRED"
]
}
}