Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
60 commits
Select commit Hold shift + click to select a range
47cdb1c
Add PV forecast providers and scheduled publishing
seaspotter Aug 8, 2026
f9980c4
Add provider-aware PV forecast daily values and manual refresh trigger
seaspotter Aug 8, 2026
8381734
Add multi-orientation forecast support and practical defaults
seaspotter Aug 8, 2026
37a7871
Remove unused PVNode scaling config fields
seaspotter Aug 8, 2026
58814f8
Fix PVNode forecast typing for Python 3.9
seaspotter Aug 8, 2026
e1eec8f
Fix forecast typing for Python 3.9 startup
seaspotter Aug 8, 2026
b7c07f2
fix(core): initialize forecast module from provider topic
seaspotter Aug 8, 2026
a66734d
feat(forecast): add dedicated ramdisk logger
seaspotter Aug 8, 2026
97ab561
feat(forecast): add provider fetch success logs
seaspotter Aug 8, 2026
5eb5319
fix(forecast): emit openmeteo completion log once
seaspotter Aug 8, 2026
91abc16
fix(forecast): always write info logs to forecast logger
seaspotter Aug 8, 2026
e72a29d
fix(forecast): keep provider config object retained
seaspotter Aug 8, 2026
b0cc8ee
fix(forecast): persist provider as object across state updates
seaspotter Aug 8, 2026
bfb96bb
fix(forecast): prevent provider mqtt reconfiguration loops
seaspotter Aug 8, 2026
b37e22b
fix(forecast): avoid republishing unchanged provider state
seaspotter Aug 8, 2026
ce05833
fix(forecast): keep legacy provider type after restart
seaspotter Aug 8, 2026
ba4f0f4
fix(security): add forecast ACL access topic defaults
seaspotter Aug 8, 2026
69d269c
fix(security): add forecast role ACLs and bump dynsec version
seaspotter Aug 8, 2026
cbd52d9
fix(security): allow wildcard subscription for settings access
seaspotter Aug 8, 2026
ab64483
fix(security): allow settings access wildcard subscribe pattern
seaspotter Aug 8, 2026
d8c0998
fix(security): assign settings access to default user group
seaspotter Aug 8, 2026
7a4bd1e
fix(security): simplify forecast acl defaults
seaspotter Aug 8, 2026
824b95b
fix(forecast): require strings and ignore legacy null provider resets
seaspotter Aug 8, 2026
b07d579
fix(forecastsolar): use unparameterized List type for strings to fix …
seaspotter Aug 9, 2026
540a3a2
fix(openmeteo): use unparameterized List type for strings to fix data…
seaspotter Aug 9, 2026
efd0c0a
fix(forecast): raise_for_status for 429 detection and register foreca…
seaspotter Aug 9, 2026
f59fb66
fix(forecast): skip null-type provider reset during startup to surviv…
seaspotter Aug 9, 2026
38782bf
fix(forecast): remove provider topic republish from setter - align wi…
seaspotter Aug 9, 2026
36b0bd5
fix(forecast): correct Open-Meteo formula, clean configs, remove dead…
seaspotter Aug 9, 2026
d7f4a77
refactor(forecast): simplify subdata provider handler to match EP pat…
seaspotter Aug 9, 2026
efae4b5
fix(forecastsolar): switch to /estimate/watts endpoint and handle fla…
seaspotter Aug 9, 2026
b9344da
feat(forecast): track and publish last_update_time after successful f…
seaspotter Aug 9, 2026
5243f25
fix(update_config): add forecast topics to valid_topic to prevent del…
seaspotter Aug 9, 2026
2df6e2e
fix(forecast): reset last_update_time when provider is removed
seaspotter Aug 9, 2026
e44f999
fix(security): revert unintended settings-access group assignment
seaspotter Aug 9, 2026
2c177c9
fix(forecast): normalize config before comparison to stop runaway upd…
seaspotter Aug 9, 2026
8ff2bad
fix(security): restore compact formatting for groups
seaspotter Aug 9, 2026
2b6e026
fix(forecast): restore next_query_time from MQTT state on provider init
seaspotter Aug 9, 2026
e77837a
refactor(forecast): use self.get reference pattern like EP modules
seaspotter Aug 9, 2026
9354d65
fix(forecast): defer first update on new provider init and handle con…
seaspotter Aug 9, 2026
c741f21
feat(forecast): defer init only when config incomplete, support all p…
seaspotter Aug 9, 2026
d0f2902
refactor(forecast): move config validation to provider modules
seaspotter Aug 9, 2026
d9e5f67
fix(forecast): block updates when config incomplete, match EP pattern
seaspotter Aug 9, 2026
8a6e0bf
debug(forecast): add debug logs to _is_update_due()
seaspotter Aug 9, 2026
14462e1
debug(forecast): change debug logs to INFO level
seaspotter Aug 9, 2026
4e4b183
fix(forecast): use property for get state to avoid stale references
seaspotter Aug 9, 2026
9883a19
fix(forecast): match EP pattern - accept get as parameter, use single…
seaspotter Aug 9, 2026
6b07d2e
debug(forecast): add detailed debug log to update() to show state
seaspotter Aug 9, 2026
773803c
fix(forecast): preserve next_query_time during provider re-initializa…
seaspotter Aug 9, 2026
523f41d
fix(forecast): use @property for get to always reference current sing…
seaspotter Aug 9, 2026
b132ae5
fix(forecast): simplify singleton reference with @property pattern
seaspotter Aug 9, 2026
88020f8
fix(forecast): point @property to correct singleton instance
seaspotter Aug 9, 2026
a8c37ae
fix(forecast): initialize next_query_time on provider creation
seaspotter Aug 9, 2026
6b07c79
refactor: consolidate forecast provider implementation
seaspotter Aug 9, 2026
ea284b9
chore: translate remaining English docstrings and comments to German
seaspotter Aug 9, 2026
b945672
chore: fix flake8 style issues
seaspotter Aug 9, 2026
4137928
chore: fix flake8 line length in loadvars.py
seaspotter Aug 9, 2026
0ea61e8
chore: add forecast topic to internal topics ACL test
seaspotter Aug 9, 2026
4dafca2
chore: add forecast topic to valid_topic list
seaspotter Aug 9, 2026
71f95d8
chore: add forecast topic to valid_topic list in correct alphabetical…
seaspotter Aug 9, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 56 additions & 1 deletion data/config/mosquitto/public/default-dynamic-security.json
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@
"anonymousGroup": "anonymous",
"roles": [
{
"rolename": "openwb-version:8",
"rolename": "openwb-version:9",
"textname": "openWB Versionsnummer",
"textdescription": "Diese Rolle ist ein Platzhalter für die openWB Versionsnummer und wird automatisch aktualisiert. Sie hat keine direkten Berechtigungen.",
"acls": []
Expand Down Expand Up @@ -471,6 +471,24 @@
"textname": "Zugang zu den Einstellungen",
"textdescription": "Erlaubt den Zugang zu den Einstellungen. Für weitere Optionen werden noch zusätzliche Rollen benötigt!",
"acls": [
{
"acltype": "subscribePattern",
"topic": "openWB/system/security/access/+",
"priority": 0,
"allow": true
},
{
"acltype": "unsubscribePattern",
"topic": "openWB/system/security/access/+",
"priority": 0,
"allow": true
},
{
"acltype": "publishClientReceive",
"topic": "openWB/system/security/access/+",
"priority": 0,
"allow": true
},
{
"acltype": "publishClientReceive",
"topic": "openWB/system/security/access/Settings",
Expand Down Expand Up @@ -1600,6 +1618,43 @@
}
]
},
{
"rolename": "forecast-configuration-access",
"textname": "Zugang zu der PV-Prognose",
"textdescription": "Erlaubt den Zugang zu der PV-Prognose.",
"acls": [
{
"acltype": "publishClientSend",
"topic": "openWB/set/optional/forecast/provider",
"priority": 0,
"allow": true
},
{
"acltype": "publishClientSend",
"topic": "openWB/set/optional/forecast/get/force_update",
"priority": 0,
"allow": true
},
{
"acltype": "publishClientReceive",
"topic": "openWB/system/security/access/ForecastConfiguration",
"priority": 0,
"allow": true
},
{
"acltype": "publishClientReceive",
"topic": "openWB/system/configurable/forecasts",
"priority": 0,
"allow": true
},
{
"acltype": "publishClientReceive",
"topic": "openWB/optional/forecast/#",
"priority": 0,
"allow": true
}
]
},
{
"rolename": "charge-point-installation-access",
"textname": "Zugang zu der Ladepunkt-Installation",
Expand Down
44 changes: 44 additions & 0 deletions packages/control/optional.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"""Optionale Module
"""
from dataclasses import asdict
import logging
from math import ceil
from threading import Thread
Expand All @@ -14,6 +15,7 @@
from helpermodules.pub import Pub
from helpermodules import timecheck
from helpermodules.utils import thread_handler
from modules.common.configurable_forecast import ConfigurableForecast
from modules.common.configurable_tariff import ConfigurableFlexibleTariff, ConfigurableGridFee
from modules.common.configurable_monitoring import ConfigurableMonitoring

Expand All @@ -28,6 +30,7 @@ def __init__(self):
self.data = OptionalData()
self._flexible_tariff_module: TypingOptional[ConfigurableFlexibleTariff] = None
self._grid_fee_module: TypingOptional[ConfigurableGridFee] = None
self._forecast_module: TypingOptional[ConfigurableForecast] = None
self.monitoring_module: TypingOptional[ConfigurableMonitoring] = None
self.data.dc_charging = hardware_configuration.get_hardware_configuration_setting("dc_charging")
Pub().pub("openWB/optional/dc_charging", self.data.dc_charging)
Expand All @@ -52,6 +55,47 @@ def flexible_tariff_module(self, value: TypingOptional[ConfigurableFlexibleTarif
def grid_fee_module(self) -> TypingOptional[ConfigurableGridFee]:
return self._grid_fee_module

@property
def forecast_module(self) -> TypingOptional[ConfigurableForecast]:
return self._forecast_module

@forecast_module.setter
def forecast_module(self, value: TypingOptional[ConfigurableForecast]):
previous_configured = self.data.forecast.configured
self._forecast_module = value
if value is None:
self.data.forecast.configured = False
self.data.forecast.provider = None
self.data.forecast.get.fault_state = 0
self.data.forecast.get.fault_str = NO_ERROR
self.data.forecast.get.force_update = False
self.data.forecast.get.values = {}
self.data.forecast.get.today_values = {}
self.data.forecast.get.tomorrow_values = {}
self.data.forecast.get.daily_kwh = {}
self.data.forecast.get.today_kwh = 0.0
self.data.forecast.get.tomorrow_kwh = 0.0
self.data.forecast.get.next_query_time = 0
self.data.forecast.get.last_update_time = 0
if previous_configured is not False:
Pub().pub("openWB/set/optional/forecast/configured", False)
Pub().pub("openWB/set/optional/forecast/get/fault_state", 0)
Pub().pub("openWB/set/optional/forecast/get/fault_str", NO_ERROR)
Pub().pub("openWB/set/optional/forecast/get/force_update", False)
Pub().pub("openWB/set/optional/forecast/get/values", {})
Pub().pub("openWB/set/optional/forecast/get/today_values", {})
Pub().pub("openWB/set/optional/forecast/get/tomorrow_values", {})
Pub().pub("openWB/set/optional/forecast/get/daily_kwh", {})
Pub().pub("openWB/set/optional/forecast/get/today_kwh", 0.0)
Pub().pub("openWB/set/optional/forecast/get/tomorrow_kwh", 0.0)
Pub().pub("openWB/set/optional/forecast/get/next_query_time", 0)
Pub().pub("openWB/set/optional/forecast/get/last_update_time", 0)
else:
self.data.forecast.configured = True
self.data.forecast.provider = asdict(value.config)
if previous_configured is not True:
Pub().pub("openWB/set/optional/forecast/configured", True)

@grid_fee_module.setter
def grid_fee_module(self, value: TypingOptional[ConfigurableGridFee]):
if (value is None or
Expand Down
49 changes: 48 additions & 1 deletion packages/control/optional_data.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,46 @@
from dataclasses import dataclass, field
from typing import Dict, Optional, Protocol
from typing import Any, Dict, Optional, Protocol, Union

from dataclass_utils.factories import empty_dict_factory
from helpermodules.constants import NO_ERROR
from modules.display_themes.cards.config import CardsDisplayTheme


@dataclass
class ForecastGet:
fault_state: int = field(default=0)
fault_str: str = field(default=NO_ERROR)
force_update: bool = field(default=False)
values: Dict = field(default_factory=empty_dict_factory)
today_values: Dict = field(default_factory=empty_dict_factory)
tomorrow_values: Dict = field(default_factory=empty_dict_factory)
daily_kwh: Dict = field(default_factory=empty_dict_factory)
today_kwh: float = field(default=0.0)
tomorrow_kwh: float = field(default=0.0)
next_query_time: int = field(default=0)
last_update_time: int = field(default=0)


def create_forecast_get_with_topics(topic_prefix: str) -> ForecastGet:
forecast_get = ForecastGet()
forecast_get.__dataclass_fields__['fault_state'].metadata = {"topic": f"{topic_prefix}/get/fault_state"}
forecast_get.__dataclass_fields__['fault_str'].metadata = {"topic": f"{topic_prefix}/get/fault_str"}
forecast_get.__dataclass_fields__['force_update'].metadata = {"topic": f"{topic_prefix}/get/force_update"}
forecast_get.__dataclass_fields__['values'].metadata = {"topic": f"{topic_prefix}/get/values"}
forecast_get.__dataclass_fields__['today_values'].metadata = {"topic": f"{topic_prefix}/get/today_values"}
forecast_get.__dataclass_fields__['tomorrow_values'].metadata = {"topic": f"{topic_prefix}/get/tomorrow_values"}
forecast_get.__dataclass_fields__['daily_kwh'].metadata = {"topic": f"{topic_prefix}/get/daily_kwh"}
forecast_get.__dataclass_fields__['today_kwh'].metadata = {"topic": f"{topic_prefix}/get/today_kwh"}
forecast_get.__dataclass_fields__['tomorrow_kwh'].metadata = {"topic": f"{topic_prefix}/get/tomorrow_kwh"}
forecast_get.__dataclass_fields__['next_query_time'].metadata = {"topic": f"{topic_prefix}/get/next_query_time"}
forecast_get.__dataclass_fields__['last_update_time'].metadata = {"topic": f"{topic_prefix}/get/last_update_time"}
return forecast_get


def forecast_get_factory() -> ForecastGet:
return create_forecast_get_with_topics("forecast")


@dataclass
class PricingGet:
fault_state: int = field(default=0)
Expand Down Expand Up @@ -77,10 +112,21 @@ class ElectricityPricing:
get: ElectricityPricingGet = field(default_factory=electricity_pricing_get_factory)


@dataclass
class Forecast:
configured: bool = field(default=False, metadata={"topic": "forecast/configured"})
provider: Optional[Union[str, Dict[str, Any]]] = field(default=None, metadata={"topic": "forecast/provider"})
get: ForecastGet = field(default_factory=forecast_get_factory)


def ep_factory() -> ElectricityPricing:
return ElectricityPricing()


def forecast_factory() -> Forecast:
return Forecast()


def cards_display_theme_factory() -> CardsDisplayTheme:
return CardsDisplayTheme()

Expand Down Expand Up @@ -136,6 +182,7 @@ def ocpp_factory() -> Ocpp:
@dataclass
class OptionalData:
electricity_pricing: ElectricityPricing = field(default_factory=ep_factory)
forecast: Forecast = field(default_factory=forecast_factory)
int_display: InternalDisplay = field(default_factory=int_display_factory)
rfid: Rfid = field(default_factory=rfid_factory)
dc_charging: bool = field(default=False, metadata={"topic": "dc_charging"})
Expand Down
1 change: 1 addition & 0 deletions packages/helpermodules/create_debug.py
Original file line number Diff line number Diff line change
Expand Up @@ -450,6 +450,7 @@ def write_to_file(file_handler, func, default: Optional[Any] = None):
df,
lambda: f'# section: internal chargepoint log #\n{merge_log_files("internal_chargepoint", 1000)}\n')
write_to_file(df, lambda: f'# section: mqtt log #\n{merge_log_files("mqtt", 1000)}\n')
write_to_file(df, lambda: f'# section: forecast log #\n{merge_log_files("forecast", 1000)}\n')
write_to_file(df, lambda: f'# section: soc log #\n{merge_log_files("soc", 1000)}\n')
write_to_file(df, lambda: f'# section: charge log #\n{merge_log_files("chargelog", 1000)}\n')
write_to_file(df, lambda: f"# section: broker #\n{broker.get_broker()}")
Expand Down
9 changes: 9 additions & 0 deletions packages/helpermodules/logger.py
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,15 @@ def mb_to_bytes(megabytes: int) -> int:
mqtt_file_handler.addFilter(RedactingFilter())
mqtt_log.addHandler(mqtt_file_handler)

# Forecast logger
forecast_log = logging.getLogger("forecast")
forecast_log.propagate = False
forecast_log.setLevel(logging.INFO)
forecast_file_handler = RotatingFileHandler(RAMDISK_PATH / 'forecast.log', maxBytes=mb_to_bytes(1), backupCount=1)
forecast_file_handler.setFormatter(logging.Formatter(FORMAT_STR_DETAILED))
forecast_file_handler.addFilter(RedactingFilter())
forecast_log.addHandler(forecast_file_handler)

# Steuve control command logger
steuve_control_command_log = logging.getLogger("steuve_control_command")
steuve_control_command_log.propagate = False
Expand Down
26 changes: 26 additions & 0 deletions packages/helpermodules/setdata.py
Original file line number Diff line number Diff line change
Expand Up @@ -882,6 +882,32 @@ def process_optional_topic(self, msg: mqtt.MQTTMessage):
self._validate_value(msg, "json")
elif "openWB/set/optional/ep/configured" in msg.topic:
self._validate_value(msg, bool)
elif "openWB/set/optional/forecast/configured" in msg.topic:
self._validate_value(msg, bool)
elif "openWB/set/optional/forecast/provider" in msg.topic:
self._validate_value(msg, "json")
elif "openWB/set/optional/forecast/get/values" in msg.topic:
self._validate_value(msg, "json")
elif "openWB/set/optional/forecast/get/today_values" in msg.topic:
self._validate_value(msg, "json")
elif "openWB/set/optional/forecast/get/tomorrow_values" in msg.topic:
self._validate_value(msg, "json")
elif "openWB/set/optional/forecast/get/daily_kwh" in msg.topic:
self._validate_value(msg, "json")
elif "openWB/set/optional/forecast/get/today_kwh" in msg.topic:
self._validate_value(msg, float)
elif "openWB/set/optional/forecast/get/tomorrow_kwh" in msg.topic:
self._validate_value(msg, float)
elif "openWB/set/optional/forecast/get/next_query_time" in msg.topic:
self._validate_value(msg, int)
elif "openWB/set/optional/forecast/get/last_update_time" in msg.topic:
self._validate_value(msg, int)
elif "openWB/set/optional/forecast/get/fault_state" in msg.topic:
self._validate_value(msg, int, [(0, 2)])
elif "openWB/set/optional/forecast/get/fault_str" in msg.topic:
self._validate_value(msg, str)
elif "openWB/set/optional/forecast/get/force_update" in msg.topic:
self._validate_value(msg, bool)
elif "module_update_completed" in msg.topic:
self._validate_value(msg, bool)
elif "openWB/set/optional/ocpp/config" in msg.topic:
Expand Down
22 changes: 22 additions & 0 deletions packages/helpermodules/subdata.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
from modules.common.abstract_vehicle import CalculatedSocState, GeneralVehicleConfig
from modules.common.component_type import ComponentType
from modules.common.configurable_backup_cloud import ConfigurableBackupCloud
from modules.common.configurable_forecast import ConfigurableForecastProvider
from modules.common.configurable_tariff import ConfigurableFlexibleTariff, ConfigurableGridFee
from modules.common.simcount.simcounter_state import SimCounterState
from modules.internal_chargepoint_handler.internal_chargepoint_handler_config import (
Expand Down Expand Up @@ -798,6 +799,27 @@ def process_optional_topic(self, var: optional.Optional, msg: mqtt.MQTTMessage):
self.set_json_payload_class(var.data.electricity_pricing.get, msg)
elif re.search("/optional/ep/", msg.topic) is not None:
self.set_json_payload_class(var.data.electricity_pricing, msg)
elif re.search("/optional/forecast/provider$", msg.topic) is not None:
config_dict = decode_payload(msg.payload)
if not isinstance(config_dict, dict) or config_dict.get("type") is None:
# Ignore during startup to avoid stale retained null payloads clearing a valid provider.
if self.event_subdata_initialized.is_set():
if var.forecast_module is not None or var.data.forecast.provider is not None:
var.data.forecast.provider = None
var.forecast_module = None
else:
var.data.forecast.provider = config_dict
mod = importlib.import_module(
f".forecast.{config_dict['type']}.forecast", "modules")
config = dataclass_from_dict(mod.device_descriptor.configuration_factory, config_dict)
# Compare parsed config (unknown fields stripped) to avoid re-init on every retained update.
current_config = asdict(var.forecast_module.config) if var.forecast_module is not None else None
if current_config != asdict(config):
var.forecast_module = ConfigurableForecastProvider(config, mod.create_forecast)
elif re.search("/optional/forecast/get/", msg.topic) is not None:
self.set_json_payload_class(var.data.forecast.get, msg)
elif re.search("/optional/forecast/", msg.topic) is not None:
self.set_json_payload_class(var.data.forecast, msg)
elif "module_update_completed" in msg.topic:
self.event_module_update_completed.set()
elif re.search("/optional/ocpp/", msg.topic) is not None:
Expand Down
16 changes: 16 additions & 0 deletions packages/helpermodules/update_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -344,6 +344,19 @@ class UpdateConfig:
"^openWB/optional/ep/grid_fee/get/fault_str$",
"^openWB/optional/ep/grid_fee/get/prices$",
"^openWB/optional/ep/grid_fee/provider$",
"^openWB/optional/forecast/configured$",
"^openWB/optional/forecast/provider$",
"^openWB/optional/forecast/get/fault_state$",
"^openWB/optional/forecast/get/fault_str$",
"^openWB/optional/forecast/get/force_update$",
"^openWB/optional/forecast/get/values$",
"^openWB/optional/forecast/get/today_values$",
"^openWB/optional/forecast/get/tomorrow_values$",
"^openWB/optional/forecast/get/daily_kwh$",
"^openWB/optional/forecast/get/today_kwh$",
"^openWB/optional/forecast/get/tomorrow_kwh$",
"^openWB/optional/forecast/get/next_query_time$",
"^openWB/optional/forecast/get/last_update_time$",
"^openWB/optional/int_display/active$",
"^openWB/optional/int_display/detected$",
"^openWB/optional/int_display/on_if_plugged_in$",
Expand Down Expand Up @@ -502,6 +515,7 @@ class UpdateConfig:
"^openWB/system/configurable/chargepoints_internal$",
"^openWB/system/configurable/devices_components$",
"^openWB/system/configurable/flexible_tariffs$",
"^openWB/system/configurable/forecasts$",
"^openWB/system/configurable/grid_fees$",
"^openWB/system/configurable/display_themes$",
"^openWB/system/configurable/io_actions$",
Expand Down Expand Up @@ -549,6 +563,7 @@ class UpdateConfig:
"^openWB/system/security/access/ActiveBatControlConfiguration$",
"^openWB/system/security/access/HardwareInstallation$",
"^openWB/system/security/access/LoadManagementConfiguration$",
"^openWB/system/security/access/ForecastConfiguration$",
"^openWB/system/security/access/ChargePointInstallation$",
"^openWB/system/security/access/VehicleConfiguration$",
"^openWB/system/security/access/IoConfiguration$",
Expand Down Expand Up @@ -686,6 +701,7 @@ class UpdateConfig:
("openWB/system/security/access/ActiveBatControlConfiguration", True),
("openWB/system/security/access/HardwareInstallation", True),
("openWB/system/security/access/LoadManagementConfiguration", True),
("openWB/system/security/access/ForecastConfiguration", True),
("openWB/system/security/access/ChargePointInstallation", True),
("openWB/system/security/access/VehicleConfiguration", True),
("openWB/system/security/access/IoConfiguration", True),
Expand Down
10 changes: 10 additions & 0 deletions packages/modules/common/component_state.py
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,16 @@ def __init__(self,
self.prices = prices


@auto_str
class ForecastState:
def __init__(self,
forecast_values: Optional[Dict[str, float]] = None,
daily_kwh: Optional[Dict[str, float]] = None
) -> None:
self.forecast_values = forecast_values
self.daily_kwh = daily_kwh


@auto_str
class IoState:
"""JSON erlaubt nur Zeichenketten als Schlüssel für Objekte"""
Expand Down
Loading