mirror of
https://github.com/Akkudoktor-EOS/EOS.git
synced 2025-04-19 08:55:15 +00:00
parent
b43bf105aa
commit
1bf49c8c52
@ -201,25 +201,38 @@ class PVForecastAkkudoktor(PVForecastProvider):
|
|||||||
|
|
||||||
def _url(self) -> str:
|
def _url(self) -> str:
|
||||||
"""Build akkudoktor.net API request URL."""
|
"""Build akkudoktor.net API request URL."""
|
||||||
url = f"https://api.akkudoktor.net/forecast?lat={self.config.latitude}&lon={self.config.longitude}&"
|
base_url = "https://api.akkudoktor.net/forecast"
|
||||||
planes_peakpower = self.config.pvforecast_planes_peakpower
|
query_params = [
|
||||||
planes_azimuth = self.config.pvforecast_planes_azimuth
|
f"lat={self.config.latitude}",
|
||||||
planes_tilt = self.config.pvforecast_planes_tilt
|
f"lon={self.config.longitude}",
|
||||||
planes_inverter_paco = self.config.pvforecast_planes_inverter_paco
|
]
|
||||||
planes_userhorizon = self.config.pvforecast_planes_userhorizon
|
|
||||||
for i, plane in enumerate(self.config.pvforecast_planes):
|
for i in range(len(self.config.pvforecast_planes)):
|
||||||
url += f"power={int(planes_peakpower[i]*1000)}&"
|
query_params.append(f"power={int(self.config.pvforecast_planes_peakpower[i] * 1000)}")
|
||||||
url += f"azimuth={int(planes_azimuth[i])}&"
|
query_params.append(f"azimuth={int(self.config.pvforecast_planes_azimuth[i])}")
|
||||||
url += f"tilt={int(planes_tilt[i])}&"
|
query_params.append(f"tilt={int(self.config.pvforecast_planes_tilt[i])}")
|
||||||
url += f"powerInverter={int(planes_inverter_paco[i])}&"
|
query_params.append(
|
||||||
url += "horizont="
|
f"powerInverter={int(self.config.pvforecast_planes_inverter_paco[i])}"
|
||||||
for horizon in planes_userhorizon[i]:
|
)
|
||||||
url += f"{int(horizon)},"
|
horizon_values = ",".join(
|
||||||
url = url[:-1] # remove trailing comma
|
str(int(h)) for h in self.config.pvforecast_planes_userhorizon[i]
|
||||||
url += "&"
|
)
|
||||||
url += "past_days=5&cellCoEff=-0.36&inverterEfficiency=0.8&albedo=0.25&"
|
query_params.append(f"horizont={horizon_values}")
|
||||||
url += f"timezone={self.config.timezone}&"
|
|
||||||
url += "hourly=relativehumidity_2m%2Cwindspeed_10m"
|
# Append fixed query parameters
|
||||||
|
query_params.extend(
|
||||||
|
[
|
||||||
|
"past_days=5",
|
||||||
|
"cellCoEff=-0.36",
|
||||||
|
"inverterEfficiency=0.8",
|
||||||
|
"albedo=0.25",
|
||||||
|
f"timezone={self.config.timezone}",
|
||||||
|
"hourly=relativehumidity_2m%2Cwindspeed_10m",
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
# Join all query parameters with `&`
|
||||||
|
url = f"{base_url}?{'&'.join(query_params)}"
|
||||||
logger.debug(f"Akkudoktor URL: {url}")
|
logger.debug(f"Akkudoktor URL: {url}")
|
||||||
return url
|
return url
|
||||||
|
|
||||||
@ -252,7 +265,7 @@ class PVForecastAkkudoktor(PVForecastProvider):
|
|||||||
`PVForecastAkkudoktorDataRecord`.
|
`PVForecastAkkudoktorDataRecord`.
|
||||||
"""
|
"""
|
||||||
# Assure we have something to request PV power for.
|
# Assure we have something to request PV power for.
|
||||||
if len(self.config.pvforecast_planes) == 0:
|
if not self.config.pvforecast_planes:
|
||||||
# No planes for PV
|
# No planes for PV
|
||||||
error_msg = "Requested PV forecast, but no planes configured."
|
error_msg = "Requested PV forecast, but no planes configured."
|
||||||
logger.error(f"Configuration error: {error_msg}")
|
logger.error(f"Configuration error: {error_msg}")
|
||||||
@ -269,34 +282,36 @@ class PVForecastAkkudoktor(PVForecastProvider):
|
|||||||
|
|
||||||
# Assumption that all lists are the same length and are ordered chronologically
|
# Assumption that all lists are the same length and are ordered chronologically
|
||||||
# in ascending order and have the same timestamps.
|
# in ascending order and have the same timestamps.
|
||||||
values_len = len(akkudoktor_data.values[0])
|
if len(akkudoktor_data.values[0]) < self.config.prediction_hours:
|
||||||
if values_len < self.config.prediction_hours:
|
|
||||||
# Expect one value set per prediction hour
|
# Expect one value set per prediction hour
|
||||||
error_msg = (
|
error_msg = (
|
||||||
f"The forecast must cover at least {self.config.prediction_hours} hours, "
|
f"The forecast must cover at least {self.config.prediction_hours} hours, "
|
||||||
f"but only {values_len} data sets are given in forecast data."
|
f"but only {len(akkudoktor_data.values[0])} data sets are given in forecast data."
|
||||||
)
|
)
|
||||||
logger.error(f"Akkudoktor schema change: {error_msg}")
|
logger.error(f"Akkudoktor schema change: {error_msg}")
|
||||||
raise ValueError(error_msg)
|
raise ValueError(error_msg)
|
||||||
|
|
||||||
for i in range(values_len):
|
assert self.start_datetime # mypy fix
|
||||||
original_datetime = akkudoktor_data.values[0][i].datetime
|
|
||||||
|
# Iterate over forecast data points
|
||||||
|
for forecast_values in zip(*akkudoktor_data.values):
|
||||||
|
original_datetime = forecast_values[0].datetime
|
||||||
dt = to_datetime(original_datetime, in_timezone=self.config.timezone)
|
dt = to_datetime(original_datetime, in_timezone=self.config.timezone)
|
||||||
|
|
||||||
# We provide prediction starting at start of day, to be compatible to old system.
|
# Skip outdated forecast data
|
||||||
if compare_datetimes(dt, self.start_datetime.start_of("day")).lt:
|
if compare_datetimes(dt, self.start_datetime.start_of("day")).lt:
|
||||||
# forecast data is too old
|
|
||||||
continue
|
continue
|
||||||
|
|
||||||
sum_dc_power = sum(values[i].dcPower for values in akkudoktor_data.values)
|
sum_dc_power = sum(values.dcPower for values in forecast_values)
|
||||||
sum_ac_power = sum(values[i].power for values in akkudoktor_data.values)
|
sum_ac_power = sum(values.power for values in forecast_values)
|
||||||
|
|
||||||
data = {
|
data = {
|
||||||
"pvforecast_dc_power": sum_dc_power,
|
"pvforecast_dc_power": sum_dc_power,
|
||||||
"pvforecast_ac_power": sum_ac_power,
|
"pvforecast_ac_power": sum_ac_power,
|
||||||
"pvforecastakkudoktor_wind_speed_10m": akkudoktor_data.values[0][i].windspeed_10m,
|
"pvforecastakkudoktor_wind_speed_10m": forecast_values[0].windspeed_10m,
|
||||||
"pvforecastakkudoktor_temp_air": akkudoktor_data.values[0][i].temperature,
|
"pvforecastakkudoktor_temp_air": forecast_values[0].temperature,
|
||||||
}
|
}
|
||||||
|
|
||||||
self.update_value(dt, data)
|
self.update_value(dt, data)
|
||||||
|
|
||||||
if len(self) < self.config.prediction_hours:
|
if len(self) < self.config.prediction_hours:
|
||||||
@ -307,35 +322,37 @@ class PVForecastAkkudoktor(PVForecastProvider):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def report_ac_power_and_measurement(self) -> str:
|
def report_ac_power_and_measurement(self) -> str:
|
||||||
"""Report DC/ AC power, and AC power measurement for each forecast hour.
|
"""Generate a report of DC power, forecasted AC power, measured AC power, and other AC power values.
|
||||||
|
|
||||||
For each forecast entry, the time, DC power, forecasted AC power, measured AC power
|
For each forecast entry, the following details are included:
|
||||||
(if available), and the value returned by the `get_ac_power` method is provided.
|
- Time of the forecast
|
||||||
|
- DC power
|
||||||
|
- Forecasted AC power
|
||||||
|
- Measured AC power (if available)
|
||||||
|
- Value returned by `get_ac_power` (if available)
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
str: The report.
|
str: A formatted report containing details for each forecast entry.
|
||||||
"""
|
"""
|
||||||
rep = ""
|
|
||||||
|
def format_value(value: float | None) -> str:
|
||||||
|
"""Helper to format values as rounded strings or 'N/A' if None."""
|
||||||
|
return f"{round(value, 2)}" if value is not None else "N/A"
|
||||||
|
|
||||||
|
report_lines = []
|
||||||
for record in self.records:
|
for record in self.records:
|
||||||
date_time = record.date_time
|
date_time = record.date_time
|
||||||
dc_pow = round(record.pvforecast_dc_power, 2) if record.pvforecast_dc_power else None
|
dc_power = format_value(record.pvforecast_dc_power)
|
||||||
ac_pow = round(record.pvforecast_ac_power, 2) if record.pvforecast_ac_power else None
|
ac_power = format_value(record.pvforecast_ac_power)
|
||||||
ac_pow_measurement = (
|
ac_power_measured = format_value(record.pvforecastakkudoktor_ac_power_measured)
|
||||||
round(record.pvforecastakkudoktor_ac_power_measured, 2)
|
ac_power_any = format_value(record.pvforecastakkudoktor_ac_power_any)
|
||||||
if record.pvforecastakkudoktor_ac_power_measured
|
|
||||||
else None
|
report_lines.append(
|
||||||
|
f"Date&Time: {date_time}, DC: {dc_power}, AC: {ac_power}, "
|
||||||
|
f"AC sampled: {ac_power_measured}, AC any: {ac_power_any}"
|
||||||
)
|
)
|
||||||
ac_pow_any = (
|
|
||||||
round(record.pvforecastakkudoktor_ac_power_any, 2)
|
return "\n".join(report_lines)
|
||||||
if record.pvforecastakkudoktor_ac_power_any
|
|
||||||
else None
|
|
||||||
)
|
|
||||||
rep += (
|
|
||||||
f"Date&Time: {date_time}, DC: {dc_pow}, AC: {ac_pow}, "
|
|
||||||
f"AC sampled: {ac_pow_measurement}, AC any: {ac_pow_any}"
|
|
||||||
"\n"
|
|
||||||
)
|
|
||||||
return rep
|
|
||||||
|
|
||||||
|
|
||||||
# Example of how to use the PVForecastAkkudoktor class
|
# Example of how to use the PVForecastAkkudoktor class
|
||||||
|
Loading…
x
Reference in New Issue
Block a user