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:
|
||||
"""Build akkudoktor.net API request URL."""
|
||||
url = f"https://api.akkudoktor.net/forecast?lat={self.config.latitude}&lon={self.config.longitude}&"
|
||||
planes_peakpower = self.config.pvforecast_planes_peakpower
|
||||
planes_azimuth = self.config.pvforecast_planes_azimuth
|
||||
planes_tilt = self.config.pvforecast_planes_tilt
|
||||
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):
|
||||
url += f"power={int(planes_peakpower[i]*1000)}&"
|
||||
url += f"azimuth={int(planes_azimuth[i])}&"
|
||||
url += f"tilt={int(planes_tilt[i])}&"
|
||||
url += f"powerInverter={int(planes_inverter_paco[i])}&"
|
||||
url += "horizont="
|
||||
for horizon in planes_userhorizon[i]:
|
||||
url += f"{int(horizon)},"
|
||||
url = url[:-1] # remove trailing comma
|
||||
url += "&"
|
||||
url += "past_days=5&cellCoEff=-0.36&inverterEfficiency=0.8&albedo=0.25&"
|
||||
url += f"timezone={self.config.timezone}&"
|
||||
url += "hourly=relativehumidity_2m%2Cwindspeed_10m"
|
||||
base_url = "https://api.akkudoktor.net/forecast"
|
||||
query_params = [
|
||||
f"lat={self.config.latitude}",
|
||||
f"lon={self.config.longitude}",
|
||||
]
|
||||
|
||||
for i in range(len(self.config.pvforecast_planes)):
|
||||
query_params.append(f"power={int(self.config.pvforecast_planes_peakpower[i] * 1000)}")
|
||||
query_params.append(f"azimuth={int(self.config.pvforecast_planes_azimuth[i])}")
|
||||
query_params.append(f"tilt={int(self.config.pvforecast_planes_tilt[i])}")
|
||||
query_params.append(
|
||||
f"powerInverter={int(self.config.pvforecast_planes_inverter_paco[i])}"
|
||||
)
|
||||
horizon_values = ",".join(
|
||||
str(int(h)) for h in self.config.pvforecast_planes_userhorizon[i]
|
||||
)
|
||||
query_params.append(f"horizont={horizon_values}")
|
||||
|
||||
# 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}")
|
||||
return url
|
||||
|
||||
@ -252,7 +265,7 @@ class PVForecastAkkudoktor(PVForecastProvider):
|
||||
`PVForecastAkkudoktorDataRecord`.
|
||||
"""
|
||||
# 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
|
||||
error_msg = "Requested PV forecast, but no planes configured."
|
||||
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
|
||||
# in ascending order and have the same timestamps.
|
||||
values_len = len(akkudoktor_data.values[0])
|
||||
if values_len < self.config.prediction_hours:
|
||||
if len(akkudoktor_data.values[0]) < self.config.prediction_hours:
|
||||
# Expect one value set per prediction hour
|
||||
error_msg = (
|
||||
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}")
|
||||
raise ValueError(error_msg)
|
||||
|
||||
for i in range(values_len):
|
||||
original_datetime = akkudoktor_data.values[0][i].datetime
|
||||
assert self.start_datetime # mypy fix
|
||||
|
||||
# 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)
|
||||
|
||||
# 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:
|
||||
# forecast data is too old
|
||||
continue
|
||||
|
||||
sum_dc_power = sum(values[i].dcPower for values in akkudoktor_data.values)
|
||||
sum_ac_power = sum(values[i].power for values in akkudoktor_data.values)
|
||||
sum_dc_power = sum(values.dcPower for values in forecast_values)
|
||||
sum_ac_power = sum(values.power for values in forecast_values)
|
||||
|
||||
data = {
|
||||
"pvforecast_dc_power": sum_dc_power,
|
||||
"pvforecast_ac_power": sum_ac_power,
|
||||
"pvforecastakkudoktor_wind_speed_10m": akkudoktor_data.values[0][i].windspeed_10m,
|
||||
"pvforecastakkudoktor_temp_air": akkudoktor_data.values[0][i].temperature,
|
||||
"pvforecastakkudoktor_wind_speed_10m": forecast_values[0].windspeed_10m,
|
||||
"pvforecastakkudoktor_temp_air": forecast_values[0].temperature,
|
||||
}
|
||||
|
||||
self.update_value(dt, data)
|
||||
|
||||
if len(self) < self.config.prediction_hours:
|
||||
@ -307,35 +322,37 @@ class PVForecastAkkudoktor(PVForecastProvider):
|
||||
)
|
||||
|
||||
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
|
||||
(if available), and the value returned by the `get_ac_power` method is provided.
|
||||
For each forecast entry, the following details are included:
|
||||
- Time of the forecast
|
||||
- DC power
|
||||
- Forecasted AC power
|
||||
- Measured AC power (if available)
|
||||
- Value returned by `get_ac_power` (if available)
|
||||
|
||||
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:
|
||||
date_time = record.date_time
|
||||
dc_pow = round(record.pvforecast_dc_power, 2) if record.pvforecast_dc_power else None
|
||||
ac_pow = round(record.pvforecast_ac_power, 2) if record.pvforecast_ac_power else None
|
||||
ac_pow_measurement = (
|
||||
round(record.pvforecastakkudoktor_ac_power_measured, 2)
|
||||
if record.pvforecastakkudoktor_ac_power_measured
|
||||
else None
|
||||
dc_power = format_value(record.pvforecast_dc_power)
|
||||
ac_power = format_value(record.pvforecast_ac_power)
|
||||
ac_power_measured = format_value(record.pvforecastakkudoktor_ac_power_measured)
|
||||
ac_power_any = format_value(record.pvforecastakkudoktor_ac_power_any)
|
||||
|
||||
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)
|
||||
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
|
||||
|
||||
return "\n".join(report_lines)
|
||||
|
||||
|
||||
# Example of how to use the PVForecastAkkudoktor class
|
||||
|
Loading…
x
Reference in New Issue
Block a user