mirror of
https://github.com/Akkudoktor-EOS/EOS.git
synced 2026-07-24 18:58:12 +00:00
fix: provider settings in top level field of configuration (#1161)
Configuration for some providers was given in the sub-field `provider_settings` combining the settings of several providers. Pydantic does not understand this very well and the configuration became cumbersome, especially in EOSdash. All provider settings from the `provider-settings` sub-field are now lifted to the top level field of the configuration. The changes are automatically migrated in the configuration. Signed-off-by: Bobby Noelte <b0661n0e17e@gmail.com>
This commit is contained in:
@@ -48,49 +48,105 @@ MIGRATION_MAP: Dict[
|
||||
None, # drop
|
||||
],
|
||||
] = {
|
||||
# 0.2.0.dev -> 0.2.0.dev
|
||||
# adapter
|
||||
# =======
|
||||
# - HomeAssistant
|
||||
"adapter/homeassistant/optimization_solution_entity_ids": (
|
||||
"adapter/homeassistant/solution_entity_ids",
|
||||
lambda v: v if isinstance(v, list) else None,
|
||||
),
|
||||
"general/data_folder_path": ("general/data_folder_path", _KEEP_DEFAULT),
|
||||
# 0.2.0 -> 0.2.0+dev
|
||||
# - NodeRed
|
||||
# devices
|
||||
# =======
|
||||
# - batteries
|
||||
"devices/batteries/0/initial_soc_percentage": None,
|
||||
# - electric_vehicles
|
||||
"devices/electric_vehicles/0/initial_soc_percentage": None,
|
||||
# elecprice
|
||||
# =========
|
||||
# - ElecPriceAkkudoktor
|
||||
# - ElecPriceEnergyCharts
|
||||
# - ElecPriceFixed
|
||||
# - ElecPriceImport
|
||||
"elecprice/provider_settings/ElecPriceImport/import_file_path": "elecprice/elecpriceimport/import_file_path",
|
||||
"elecprice/provider_settings/ElecPriceImport/import_json": "elecprice/elecpriceimport/import_json",
|
||||
"load/provider_settings/LoadAkkudoktor/loadakkudoktor_year_energy_kwh": "load/loadakkudoktor/loadakkudoktor_year_energy_kwh",
|
||||
"load/provider_settings/LoadVrm/load_vrm_idsite": "load/loadvrm/load_vrm_idsite",
|
||||
"load/provider_settings/LoadVrm/load_vrm_token": "load/loadvrm/load_vrm_token",
|
||||
"load/provider_settings/LoadImport/import_file_path": "load/loadimport/import_file_path",
|
||||
"load/provider_settings/LoadImport/import_json": "load/loadimport/import_json",
|
||||
# 0.1.0 -> 0.2.0+dev
|
||||
"devices/batteries/0/initial_soc_percentage": None,
|
||||
"devices/electric_vehicles/0/initial_soc_percentage": None,
|
||||
"elecprice/provider_settings/import_file_path": "elecprice/elecpriceimport/import_file_path",
|
||||
"elecprice/provider_settings/import_json": "elecprice/elecpriceimport/import_json",
|
||||
# feedintariff
|
||||
# ============
|
||||
# - FeedInTariffFixed
|
||||
"feedintariff/provider_settings/FeedInTariffFixed/feed_in_tariff_kwh": "feedintariff/feedintarifffixed/feed_in_tariff_kwh",
|
||||
# - FeedInTariffImport
|
||||
"feedintariff/provider_settings/FeedInTariffImport/import_file_path": "feedintariff/feedintariffimport/import_file_path",
|
||||
"feedintariff/provider_settings/FeedInTariffImport/import_json": "feedintariff/feedintariffimport/import_json",
|
||||
# general
|
||||
# =======
|
||||
"general/data_folder_path": ("general/data_folder_path", _KEEP_DEFAULT),
|
||||
# load
|
||||
# ====
|
||||
# - LoadAkkudoktor
|
||||
"load/provider_settings/LoadAkkudoktor/loadakkudoktor_year_energy_kwh": "load/loadakkudoktor/loadakkudoktor_year_energy_kwh",
|
||||
"load/provider_settings/loadakkudoktor_year_energy": "load/loadakkudoktor/loadakkudoktor_year_energy_kwh",
|
||||
# - LoadVrm
|
||||
"load/loadvrm/load_vrm_idsite": "load/vrm/site_id",
|
||||
"load/loadvrm/load_vrm_token": "load/vrm/token",
|
||||
"load/provider_settings/load_vrm_idsite": "load/vrm/site_id",
|
||||
"load/provider_settings/load_vrm_token": "load/vrm/token",
|
||||
"load/provider_settings/LoadVrm/load_vrm_idsite": "load/vrm/site_id",
|
||||
"load/provider_settings/LoadVrm/load_vrm_token": "load/vrm/token",
|
||||
# - LoadImport
|
||||
"load/provider_settings/LoadImport/import_file_path": "load/loadimport/import_file_path",
|
||||
"load/provider_settings/LoadImport/import_json": "load/loadimport/import_json",
|
||||
"load/provider_settings/import_file_path": "load/loadimport/import_file_path",
|
||||
"load/provider_settings/import_json": "load/loadimport/import_json",
|
||||
"load/provider_settings/loadakkudoktor_year_energy": "load/loadakkudoktor/loadakkudoktor_year_energy_kwh",
|
||||
"load/provider_settings/load_vrm_idsite": "load/loadvrm/load_vrm_idsite",
|
||||
"load/provider_settings/load_vrm_token": "load/loadvrm/load_vrm_token",
|
||||
# logging
|
||||
# =======
|
||||
"logging/level": "logging/console_level",
|
||||
"logging/root_level": None,
|
||||
# measurement
|
||||
# ===========
|
||||
"measurement/load0_name": "measurement/load_emr_keys/0",
|
||||
"measurement/load1_name": "measurement/load_emr_keys/1",
|
||||
"measurement/load2_name": "measurement/load_emr_keys/2",
|
||||
"measurement/load3_name": "measurement/load_emr_keys/3",
|
||||
"measurement/load4_name": "measurement/load_emr_keys/4",
|
||||
# optimization
|
||||
# ============
|
||||
"optimization/ev_available_charge_rates_percent": (
|
||||
"devices/electric_vehicles/0/charge_rates",
|
||||
lambda v: [x / 100 for x in v],
|
||||
),
|
||||
"optimization/hours": "optimization/horizon_hours",
|
||||
"optimization/penalty": ("optimization/genetic/penalties/ev_soc_miss", lambda v: float(v)),
|
||||
"pvforecast/provider_settings/import_file_path": "pvforecast/provider_settings/PVForecastImport/import_file_path",
|
||||
"pvforecast/provider_settings/import_json": "pvforecast/provider_settings/PVForecastImport/import_json",
|
||||
"pvforecast/provider_settings/load_vrm_idsite": "pvforecast/provider_settings/PVForecastVrm/load_vrm_idsite",
|
||||
"pvforecast/provider_settings/load_vrm_token": "pvforecast/provider_settings/PVForecastVrm/load_vrm_token",
|
||||
"weather/provider_settings/import_file_path": "weather/provider_settings/WeatherImport/import_file_path",
|
||||
"weather/provider_settings/import_json": "weather/provider_settings/WeatherImport/import_json",
|
||||
# pvforecast
|
||||
# ==========
|
||||
# - PVForecastAkkudoktor
|
||||
# - PVForecastVrm
|
||||
"pvforecast/provider_settings/PVForecastVrm/pvforecast_vrm_token": "pvforecast/vrm/token",
|
||||
"pvforecast/provider_settings/PVForecastVrm/pvforecast_vrm_idsite": "pvforecast/vrm/site_id",
|
||||
"pvforecast/provider_settings/load_vrm_idsite": "pvforecast/vrm/site_id",
|
||||
"pvforecast/provider_settings/load_vrm_token": "pvforecast/vrm/token",
|
||||
# - PVForecastPVNode
|
||||
"pvforecast/provider_settings/PVForecastPVNode/api_key": "pvforecast/pvnode/api_key",
|
||||
"pvforecast/provider_settings/PVForecastPVNode/site_id": "pvforecast/pvnode/site_id",
|
||||
"pvforecast/provider_settings/PVForecastPVNode/forecast_days": "pvforecast/pvnode/forecast_days",
|
||||
# - PVForecastForecastSolar
|
||||
"pvforecast/provider_settings/PVForecastForecastSolar/api_key": "pvforecast/forecastsolar/api_key",
|
||||
# - PVForecastSolcast
|
||||
"pvforecast/provider_settings/PVForecastSolcast/api_key": "pvforecast/solcast/api_key",
|
||||
"pvforecast/provider_settings/PVForecastSolcast/site_id": "pvforecast/solcast/site_id",
|
||||
# - PVForecastImport
|
||||
"pvforecast/provider_settings/PVForecastImport/import_file_path": "pvforecast/pvforecastimport/import_file_path",
|
||||
"pvforecast/provider_settings/PVForecastImport/import_json": "pvforecast/pvforecastimport/import_json",
|
||||
"pvforecast/provider_settings/import_file_path": "pvforecast/pvforecastimport/import_file_path",
|
||||
"pvforecast/provider_settings/import_json": "pvforecast/pvforecastimport/import_json",
|
||||
# weather
|
||||
# =======
|
||||
# - WeatherImport
|
||||
"weather/provider_settings/WeatherImport/import_file_path": "weather/weatherimport/import_file_path",
|
||||
"weather/provider_settings/WeatherImport/import_json": "weather/weatherimport/import_json",
|
||||
"weather/provider_settings/import_file_path": "weather/weatherimport/import_file_path",
|
||||
"weather/provider_settings/import_json": "weather/weatherimport/import_json",
|
||||
}
|
||||
|
||||
# -----------------------------
|
||||
|
||||
@@ -392,10 +392,8 @@ class GeneticOptimizationParameters(
|
||||
{
|
||||
"feedintariff": {
|
||||
"provider": "FeedInTariffFixed",
|
||||
"provider_settings": {
|
||||
"FeedInTariffFixed": {
|
||||
"feed_in_tariff_kwh": 0.078,
|
||||
},
|
||||
"feedintarifffixed": {
|
||||
"feed_in_tariff_kwh": 0.078,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -70,7 +70,7 @@ class ElecPriceCommonSettings(SettingsBaseModel):
|
||||
|
||||
elecpriceimport: ElecPriceImportCommonSettings = Field(
|
||||
default_factory=ElecPriceImportCommonSettings,
|
||||
json_schema_extra={"description": "Import provider settings."},
|
||||
json_schema_extra={"description": "Electricity price import provider settings."},
|
||||
)
|
||||
|
||||
energycharts: ElecPriceEnergyChartsCommonSettings = Field(
|
||||
|
||||
@@ -25,19 +25,6 @@ def feedintariff_provider_ids() -> list[str]:
|
||||
]
|
||||
|
||||
|
||||
class FeedInTariffCommonProviderSettings(SettingsBaseModel):
|
||||
"""Feed In Tariff Prediction Provider Configuration."""
|
||||
|
||||
FeedInTariffFixed: Optional[FeedInTariffFixedCommonSettings] = Field(
|
||||
default=None,
|
||||
json_schema_extra={"description": "FeedInTariffFixed settings", "examples": [None]},
|
||||
)
|
||||
FeedInTariffImport: Optional[FeedInTariffImportCommonSettings] = Field(
|
||||
default=None,
|
||||
json_schema_extra={"description": "FeedInTariffImport settings", "examples": [None]},
|
||||
)
|
||||
|
||||
|
||||
class FeedInTariffCommonSettings(SettingsBaseModel):
|
||||
"""Feed In Tariff Prediction Configuration."""
|
||||
|
||||
@@ -49,18 +36,14 @@ class FeedInTariffCommonSettings(SettingsBaseModel):
|
||||
},
|
||||
)
|
||||
|
||||
provider_settings: FeedInTariffCommonProviderSettings = Field(
|
||||
default_factory=FeedInTariffCommonProviderSettings,
|
||||
json_schema_extra={
|
||||
"description": "Provider settings",
|
||||
"examples": [
|
||||
# Example 1: Empty/default settings (all providers None)
|
||||
{
|
||||
"FeedInTariffFixed": None,
|
||||
"FeedInTariffImport": None,
|
||||
},
|
||||
],
|
||||
},
|
||||
feedintarifffixed: FeedInTariffFixedCommonSettings = Field(
|
||||
default_factory=FeedInTariffFixedCommonSettings,
|
||||
json_schema_extra={"description": "Fixed feed in tariff provider settings."},
|
||||
)
|
||||
|
||||
feedintariffimport: FeedInTariffImportCommonSettings = Field(
|
||||
default_factory=FeedInTariffImportCommonSettings,
|
||||
json_schema_extra={"description": "Feed in tarif import provider settings."},
|
||||
)
|
||||
|
||||
@computed_field # type: ignore[prop-decorator]
|
||||
|
||||
@@ -37,9 +37,7 @@ class FeedInTariffFixed(FeedInTariffProvider):
|
||||
async def _update_data(self, force_update: Optional[bool] = False) -> None:
|
||||
error_msg = "Feed in tariff not provided"
|
||||
try:
|
||||
feed_in_tariff = (
|
||||
self.config.feedintariff.provider_settings.FeedInTariffFixed.feed_in_tariff_kwh
|
||||
)
|
||||
feed_in_tariff = self.config.feedintariff.feedintarifffixed.feed_in_tariff_kwh
|
||||
except Exception:
|
||||
logger.exception(error_msg)
|
||||
raise ValueError(error_msg)
|
||||
|
||||
@@ -67,16 +67,16 @@ class FeedInTariffImport(FeedInTariffProvider, PredictionImportProvider):
|
||||
async def _update_data(self, force_update: Optional[bool] = False) -> None:
|
||||
# Both _sequence_lock and _record_lock are already held by the caller.
|
||||
# Use internal sync methods only — never await public async counterparts.
|
||||
if self.config.feedintariff.provider_settings.FeedInTariffImport is None:
|
||||
if self.config.feedintariff.feedintariffimport is None:
|
||||
logger.debug(f"{self.provider_id()} data update without provider settings.")
|
||||
return
|
||||
if self.config.feedintariff.provider_settings.FeedInTariffImport.import_file_path:
|
||||
if self.config.feedintariff.feedintariffimport.import_file_path:
|
||||
await self._import_from_file(
|
||||
self.config.provider_settings.FeedInTariffImport.import_file_path,
|
||||
self.config.feedintariffimport.import_file_path,
|
||||
key_prefix="feedintariff",
|
||||
)
|
||||
if self.config.feedintariff.provider_settings.FeedInTariffImport.import_json:
|
||||
if self.config.feedintariff.feedintariffimport.import_json:
|
||||
await self._import_from_json(
|
||||
self.config.feedintariff.provider_settings.FeedInTariffImport.import_json,
|
||||
self.config.feedintariff.feedintariffimport.import_json,
|
||||
key_prefix="feedintariff",
|
||||
)
|
||||
|
||||
@@ -44,16 +44,16 @@ class LoadCommonSettings(SettingsBaseModel):
|
||||
json_schema_extra={"description": "LoadAkkudoktor provider settings."},
|
||||
)
|
||||
|
||||
loadvrm: LoadVrmCommonSettings = Field(
|
||||
default_factory=LoadVrmCommonSettings,
|
||||
json_schema_extra={"description": "LoadVrm provider settings."},
|
||||
)
|
||||
|
||||
loadimport: LoadImportCommonSettings = Field(
|
||||
default_factory=LoadImportCommonSettings,
|
||||
json_schema_extra={"description": "LoadImport provider settings."},
|
||||
)
|
||||
|
||||
vrm: LoadVrmCommonSettings = Field(
|
||||
default_factory=LoadVrmCommonSettings,
|
||||
json_schema_extra={"description": "Victron Remote Management (VRM) provider settings."},
|
||||
)
|
||||
|
||||
@computed_field # type: ignore[prop-decorator]
|
||||
@property
|
||||
def providers(self) -> list[str]:
|
||||
|
||||
@@ -26,14 +26,14 @@ class VrmForecastResponse(PydanticBaseModel):
|
||||
class LoadVrmCommonSettings(SettingsBaseModel):
|
||||
"""Common settings for load forecast VRM API."""
|
||||
|
||||
load_vrm_token: str = Field(
|
||||
token: str = Field(
|
||||
default="your-token",
|
||||
json_schema_extra={
|
||||
"description": "Token for Connecting VRM API",
|
||||
"description": "Access token for connecting to the Victron Remote Management (VRM) API",
|
||||
"examples": ["your-token"],
|
||||
},
|
||||
)
|
||||
load_vrm_idsite: int = Field(
|
||||
site_id: int = Field(
|
||||
default=12345, json_schema_extra={"description": "VRM-Installation-ID", "examples": [12345]}
|
||||
)
|
||||
|
||||
@@ -62,9 +62,9 @@ class LoadVrm(LoadProvider):
|
||||
def _request_forecast(self, start_ts: int, end_ts: int) -> VrmForecastResponse:
|
||||
"""Fetch forecast data from Victron VRM API."""
|
||||
base_url = "https://vrmapi.victronenergy.com/v2/installations"
|
||||
vrm_settings = self.config.load.loadvrm
|
||||
installation_id = vrm_settings.load_vrm_idsite
|
||||
api_token = vrm_settings.load_vrm_token
|
||||
vrm_settings = self.config.load.vrm
|
||||
installation_id = vrm_settings.site_id
|
||||
api_token = vrm_settings.token
|
||||
|
||||
url = f"{base_url}/{installation_id}/stats?type=forecast&start={start_ts}&end={end_ts}&interval=hours"
|
||||
headers = {"X-Authorization": f"Token {api_token}", "Content-Type": "application/json"}
|
||||
|
||||
@@ -180,31 +180,6 @@ class PVForecastPlaneSetting(SettingsBaseModel):
|
||||
return pvtechchoice
|
||||
|
||||
|
||||
class PVForecastCommonProviderSettings(SettingsBaseModel):
|
||||
"""PV Forecast Provider Configuration."""
|
||||
|
||||
PVForecastImport: Optional[PVForecastImportCommonSettings] = Field(
|
||||
default=None,
|
||||
json_schema_extra={"description": "PVForecastImport settings", "examples": [None]},
|
||||
)
|
||||
PVForecastVrm: Optional[PVForecastVrmCommonSettings] = Field(
|
||||
default=None,
|
||||
json_schema_extra={"description": "PVForecastVrm settings", "examples": [None]},
|
||||
)
|
||||
PVForecastPVNode: Optional[PVForecastPVNodeCommonSettings] = Field(
|
||||
default=None,
|
||||
json_schema_extra={"description": "PVForecastPVNode settings", "examples": [None]},
|
||||
)
|
||||
PVForecastForecastSolar: Optional[PVForecastForecastSolarCommonSettings] = Field(
|
||||
default=None,
|
||||
json_schema_extra={"description": "PVForecastForecastSolar settings", "examples": [None]},
|
||||
)
|
||||
PVForecastSolcast: Optional[PVForecastSolcastCommonSettings] = Field(
|
||||
default=None,
|
||||
json_schema_extra={"description": "PVForecastSolcast settings", "examples": [None]},
|
||||
)
|
||||
|
||||
|
||||
class PVForecastCommonSettings(SettingsBaseModel):
|
||||
"""PV Forecast Configuration."""
|
||||
|
||||
@@ -221,18 +196,29 @@ class PVForecastCommonSettings(SettingsBaseModel):
|
||||
},
|
||||
)
|
||||
|
||||
provider_settings: PVForecastCommonProviderSettings = Field(
|
||||
default_factory=PVForecastCommonProviderSettings,
|
||||
json_schema_extra={
|
||||
"description": "Provider settings",
|
||||
"examples": [
|
||||
# Example 1: Empty/default settings (all providers None)
|
||||
{
|
||||
"PVForecastImport": None,
|
||||
"PVForecastVrm": None,
|
||||
},
|
||||
],
|
||||
},
|
||||
pvforecastimport: PVForecastImportCommonSettings = Field(
|
||||
default_factory=PVForecastImportCommonSettings,
|
||||
json_schema_extra={"description": "PV forecast import provider settings"},
|
||||
)
|
||||
|
||||
vrm: PVForecastVrmCommonSettings = Field(
|
||||
default_factory=PVForecastVrmCommonSettings,
|
||||
json_schema_extra={"description": "Victron Remote Management (VRM) provider settings"},
|
||||
)
|
||||
|
||||
pvnode: PVForecastPVNodeCommonSettings = Field(
|
||||
default_factory=PVForecastPVNodeCommonSettings,
|
||||
json_schema_extra={"description": "PVNode provider settings"},
|
||||
)
|
||||
|
||||
forecastsolar: PVForecastForecastSolarCommonSettings = Field(
|
||||
default_factory=PVForecastForecastSolarCommonSettings,
|
||||
json_schema_extra={"description": "ForecastSolar provider settings"},
|
||||
)
|
||||
|
||||
solcast: PVForecastSolcastCommonSettings = Field(
|
||||
default_factory=PVForecastSolcastCommonSettings,
|
||||
json_schema_extra={"description": "Solcast provider settings"},
|
||||
)
|
||||
|
||||
planes: Optional[list[PVForecastPlaneSetting]] = Field(
|
||||
|
||||
@@ -62,7 +62,7 @@ class PVForecastForecastSolar(PVForecastProvider):
|
||||
|
||||
@property
|
||||
def _api_key(self) -> Optional[str]:
|
||||
settings = self.config.pvforecast.provider_settings.PVForecastForecastSolar
|
||||
settings = self.config.pvforecast.forecastsolar
|
||||
return settings.api_key if settings is not None else None
|
||||
|
||||
def _to_utc_datetime(self, local_ts: Any, iana_tz: Optional[str]) -> Any:
|
||||
|
||||
@@ -67,16 +67,16 @@ class PVForecastImport(PVForecastProvider, PredictionImportProvider):
|
||||
async def _update_data(self, force_update: Optional[bool] = False) -> None:
|
||||
# Both _sequence_lock and _record_lock are already held by the caller.
|
||||
# Use internal sync methods only — never await public async counterparts.
|
||||
if self.config.pvforecast.provider_settings.PVForecastImport is None:
|
||||
if self.config.pvforecast.pvforecastimport is None:
|
||||
logger.debug(f"{self.provider_id()} data update without provider settings.")
|
||||
return
|
||||
if self.config.pvforecast.provider_settings.PVForecastImport.import_file_path is not None:
|
||||
if self.config.pvforecast.pvforecastimport.import_file_path is not None:
|
||||
await self._import_from_file(
|
||||
self.config.pvforecast.provider_settings.PVForecastImport.import_file_path,
|
||||
self.config.pvforecast.pvforecastimport.import_file_path,
|
||||
key_prefix="pvforecast",
|
||||
)
|
||||
if self.config.pvforecast.provider_settings.PVForecastImport.import_json is not None:
|
||||
if self.config.pvforecast.pvforecastimport.import_json is not None:
|
||||
await self._import_from_json(
|
||||
self.config.pvforecast.provider_settings.PVForecastImport.import_json,
|
||||
self.config.pvforecast.pvforecastimport.import_json,
|
||||
key_prefix="pvforecast",
|
||||
)
|
||||
|
||||
@@ -16,7 +16,7 @@ null is treated as 0 W so the optimizer's linear resampling does not interpolate
|
||||
phantom production across the night.
|
||||
|
||||
Notes:
|
||||
- Requires ``pvforecast.provider_settings.PVForecastPVNode.api_key`` (Bearer auth).
|
||||
- Requires ``pvforecast.pvnode.api_key`` (Bearer auth).
|
||||
- API: https://api.pvnode.com/v2 (15-minute resolution).
|
||||
"""
|
||||
|
||||
@@ -81,7 +81,7 @@ class PVForecastPVNode(PVForecastProvider):
|
||||
|
||||
@property
|
||||
def _settings(self) -> PVForecastPVNodeCommonSettings:
|
||||
settings = self.config.pvforecast.provider_settings.PVForecastPVNode
|
||||
settings = self.config.pvforecast.pvnode
|
||||
if settings is None:
|
||||
settings = PVForecastPVNodeCommonSettings()
|
||||
return settings
|
||||
@@ -158,7 +158,7 @@ class PVForecastPVNode(PVForecastProvider):
|
||||
settings = self._settings
|
||||
api_key = settings.api_key
|
||||
if not api_key:
|
||||
raise ValueError("PVForecastPVNode requires pvforecast...PVForecastPVNode.api_key")
|
||||
raise ValueError("PVForecastPVNode requires pvforecast.pvnode.api_key")
|
||||
|
||||
headers = {"Authorization": f"Bearer {api_key}", "Accept": "application/json"}
|
||||
params = {"forecast_days": str(settings.forecast_days)}
|
||||
@@ -189,7 +189,7 @@ class PVForecastPVNode(PVForecastProvider):
|
||||
if latitude is None or longitude is None:
|
||||
raise ValueError(
|
||||
"PVForecastPVNode inline mode needs general.latitude/longitude "
|
||||
"(or set pvforecast...PVForecastPVNode.site_id)"
|
||||
"(or set pvforecast.pvnode.site_id)"
|
||||
)
|
||||
planes = self.config.pvforecast.planes or []
|
||||
strings = []
|
||||
|
||||
@@ -63,7 +63,7 @@ class PVForecastSolcast(PVForecastProvider):
|
||||
|
||||
@property
|
||||
def _settings(self) -> PVForecastSolcastCommonSettings:
|
||||
settings = self.config.pvforecast.provider_settings.PVForecastSolcast
|
||||
settings = self.config.pvforecast.solcast
|
||||
if settings is None:
|
||||
settings = PVForecastSolcastCommonSettings()
|
||||
return settings
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
"""Retrieves pvforecast data from VRM API."""
|
||||
"""Retrieves pvforecast data from Victron Remote Management (VRM) API."""
|
||||
|
||||
from typing import Any, Optional, Union
|
||||
|
||||
@@ -26,14 +26,14 @@ class VrmForecastResponse(PydanticBaseModel):
|
||||
class PVForecastVrmCommonSettings(SettingsBaseModel):
|
||||
"""Common settings for PV forecast VRM API."""
|
||||
|
||||
pvforecast_vrm_token: str = Field(
|
||||
token: str = Field(
|
||||
default="your-token",
|
||||
json_schema_extra={
|
||||
"description": "Token for Connecting VRM API",
|
||||
"description": "Access token for connecting to the Victron Remote Management (VRM) API",
|
||||
"examples": ["your-token"],
|
||||
},
|
||||
)
|
||||
pvforecast_vrm_idsite: int = Field(
|
||||
site_id: int = Field(
|
||||
default=12345, json_schema_extra={"description": "VRM-Installation-ID", "examples": [12345]}
|
||||
)
|
||||
|
||||
@@ -63,8 +63,8 @@ class PVForecastVrm(PVForecastProvider):
|
||||
def _request_forecast(self, start_ts: int, end_ts: int) -> VrmForecastResponse:
|
||||
"""Fetch forecast data from Victron VRM API."""
|
||||
source = "https://vrmapi.victronenergy.com/v2/installations"
|
||||
id_site = self.config.pvforecast.provider_settings.PVForecastVrm.pvforecast_vrm_idsite
|
||||
api_token = self.config.pvforecast.provider_settings.PVForecastVrm.pvforecast_vrm_token
|
||||
id_site = self.config.pvforecast.vrm.site_id
|
||||
api_token = self.config.pvforecast.vrm.token
|
||||
headers = {"X-Authorization": f"Token {api_token}", "Content-Type": "application/json"}
|
||||
url = f"{source}/{id_site}/stats?type=forecast&start={start_ts}&end={end_ts}&interval=hours"
|
||||
logger.debug(f"Requesting VRM forecast: {url}")
|
||||
|
||||
@@ -26,15 +26,6 @@ def weather_provider_ids() -> list[str]:
|
||||
]
|
||||
|
||||
|
||||
class WeatherCommonProviderSettings(SettingsBaseModel):
|
||||
"""Weather Forecast Provider Configuration."""
|
||||
|
||||
WeatherImport: Optional[WeatherImportCommonSettings] = Field(
|
||||
default=None,
|
||||
json_schema_extra={"description": "WeatherImport settings", "examples": [None]},
|
||||
)
|
||||
|
||||
|
||||
class WeatherCommonSettings(SettingsBaseModel):
|
||||
"""Weather Forecast Configuration."""
|
||||
|
||||
@@ -46,17 +37,9 @@ class WeatherCommonSettings(SettingsBaseModel):
|
||||
},
|
||||
)
|
||||
|
||||
provider_settings: WeatherCommonProviderSettings = Field(
|
||||
default_factory=WeatherCommonProviderSettings,
|
||||
json_schema_extra={
|
||||
"description": "Provider settings",
|
||||
"examples": [
|
||||
# Example 1: Empty/default settings (all providers None)
|
||||
{
|
||||
"WeatherImport": None,
|
||||
},
|
||||
],
|
||||
},
|
||||
weatherimport: WeatherImportCommonSettings = Field(
|
||||
default_factory=WeatherImportCommonSettings,
|
||||
json_schema_extra={"description": "Weather import provider settings"},
|
||||
)
|
||||
|
||||
@computed_field # type: ignore[prop-decorator]
|
||||
|
||||
@@ -67,16 +67,16 @@ class WeatherImport(WeatherProvider, PredictionImportProvider):
|
||||
async def _update_data(self, force_update: Optional[bool] = False) -> None:
|
||||
# Both _sequence_lock and _record_lock are already held by the caller.
|
||||
# Use internal sync methods only — never await public async counterparts.
|
||||
if self.config.weather.provider_settings.WeatherImport is None:
|
||||
if self.config.weather.weatherimport is None:
|
||||
logger.debug(f"{self.provider_id()} data update without provider settings.")
|
||||
return
|
||||
if self.config.weather.provider_settings.WeatherImport.import_file_path:
|
||||
if self.config.weather.weatherimport.import_file_path:
|
||||
await self._import_from_file(
|
||||
self.config.weather.provider_settings.WeatherImport.import_file_path,
|
||||
self.config.weather.weatherimport.import_file_path,
|
||||
key_prefix="weather",
|
||||
)
|
||||
if self.config.weather.provider_settings.WeatherImport.import_json:
|
||||
if self.config.weather.weatherimport.import_json:
|
||||
await self._import_from_json(
|
||||
self.config.weather.provider_settings.WeatherImport.import_json,
|
||||
self.config.weather.weatherimport.import_json,
|
||||
key_prefix="weather",
|
||||
)
|
||||
|
||||
@@ -177,7 +177,7 @@ def LoadForecast(predictions: pd.DataFrame, config: dict, date_time_tz: str, dar
|
||||
"date_time",
|
||||
"loadforecast_power_w",
|
||||
source=source,
|
||||
legend_label="Load forcast value (adjusted by measurement)",
|
||||
legend_label="Load forecast value (adjusted by measurement)",
|
||||
color="red",
|
||||
)
|
||||
plot.line(
|
||||
|
||||
@@ -1314,7 +1314,7 @@ async def fastapi_gesamtlast(request: GesamtlastRequest) -> list[float]:
|
||||
detail=f"Can not update predictions: {e}",
|
||||
)
|
||||
|
||||
# Get the forcast starting at start of day
|
||||
# Get the forecast starting at start of day
|
||||
start_datetime = to_datetime().start_of("day")
|
||||
end_datetime = start_datetime.add(days=2)
|
||||
try:
|
||||
@@ -1374,7 +1374,7 @@ async def fastapi_gesamtlast_simple(year_energy: float) -> list[float]:
|
||||
detail=f"Can not update predictions: {e}",
|
||||
)
|
||||
|
||||
# Get the forcast starting at start of day
|
||||
# Get the forecast starting at start of day
|
||||
start_datetime = to_datetime().start_of("day")
|
||||
end_datetime = start_datetime.add(days=2)
|
||||
try:
|
||||
@@ -1430,7 +1430,7 @@ async def fastapi_pvforecast() -> ForecastResponse:
|
||||
detail=f"Can not update predictions: {e}",
|
||||
)
|
||||
|
||||
# Get the forcast starting at start of day
|
||||
# Get the forecast starting at start of day
|
||||
start_datetime = to_datetime().start_of("day")
|
||||
end_datetime = start_datetime.add(days=2)
|
||||
try:
|
||||
|
||||
Reference in New Issue
Block a user