mirror of
				https://github.com/Akkudoktor-EOS/EOS.git
				synced 2025-11-04 08:46:20 +00:00 
			
		
		
		
	@@ -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
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user