| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182 |
- # -*- coding: utf-8 -*-
- """
- OpenWeatherMap Wetter-Provider.
- Erfordert einen kostenlosen API-Key.
- https://openweathermap.org/api
- """
- from datetime import date, datetime
- import httpx
- from plugins.weather.models import ForecastDay, WeatherData, WeatherResult
- from plugins.weather.providers.base import WeatherProvider
- from trixy_core.utils.debug import pdebug, perror
- class OpenWeatherMapProvider(WeatherProvider):
- """Wetter-Provider ueber die OpenWeatherMap API."""
- BASE_URL = "https://api.openweathermap.org/data/2.5"
- def __init__(self, config: dict, http=None) -> None:
- super().__init__(name="openweathermap", config=config, http=http)
- self._api_key = config.get("api_key", "")
- async def get_weather(
- self, lat: float, lon: float, location_name: str = ""
- ) -> WeatherResult | None:
- """Ruft Wetterdaten von OpenWeatherMap ab."""
- if not self._api_key:
- perror("OpenWeatherMap: Kein API-Key konfiguriert")
- return None
- base_params = {
- "lat": lat,
- "lon": lon,
- "appid": self._api_key,
- "lang": "de",
- "units": "metric",
- }
- try:
- async with httpx.AsyncClient(timeout=10.0) as client:
- # Aktuelles Wetter
- current_resp = await client.get(
- f"{self.BASE_URL}/weather", params=base_params
- )
- current_resp.raise_for_status()
- current_data = current_resp.json()
- # Vorhersage (5 Tage / 3 Stunden)
- forecast_resp = await client.get(
- f"{self.BASE_URL}/forecast", params=base_params
- )
- forecast_resp.raise_for_status()
- forecast_data = forecast_resp.json()
- return self._parse_response(current_data, forecast_data, location_name)
- except Exception as e:
- perror(f"OpenWeatherMap: Fehler beim Abruf: {e}")
- return None
- async def health_check(self) -> bool:
- """Prueft ob die OpenWeatherMap API erreichbar ist."""
- if not self._api_key:
- return False
- try:
- async with httpx.AsyncClient(timeout=5.0) as client:
- response = await client.get(
- f"{self.BASE_URL}/weather",
- params={
- "lat": 49.25,
- "lon": 8.88,
- "appid": self._api_key,
- "units": "metric",
- },
- )
- return response.status_code == 200
- except Exception:
- return False
- def _parse_response(
- self,
- current_data: dict,
- forecast_data: dict,
- location_name: str,
- ) -> WeatherResult | None:
- """Parst die OpenWeatherMap API-Antworten."""
- try:
- main = current_data.get("main", {})
- wind = current_data.get("wind", {})
- weather = current_data.get("weather", [{}])[0]
- clouds = current_data.get("clouds", {})
- rain = current_data.get("rain", {})
- # Windgeschwindigkeit: m/s -> km/h
- wind_speed_ms = wind.get("speed", 0.0)
- wind_speed_kmh = wind_speed_ms * 3.6
- name = location_name or current_data.get("name", "Unbekannt")
- current_weather = WeatherData(
- location=name,
- temperature=main.get("temp", 0.0),
- feels_like=main.get("feels_like"),
- humidity=int(main.get("humidity", 0)),
- description=weather.get("description", "").capitalize(),
- wind_speed=round(wind_speed_kmh, 1),
- wind_direction=int(wind.get("deg", 0)),
- precipitation=rain.get("1h", 0.0),
- cloud_cover=clouds.get("all"),
- pressure=main.get("pressure"),
- provider_name=self.name,
- timestamp=datetime.now(),
- raw_data=current_data,
- )
- # Vorhersage parsen
- forecast = self._parse_forecast(forecast_data)
- return WeatherResult(
- current=current_weather,
- forecast=forecast,
- fetched_at=datetime.now(),
- )
- except Exception as e:
- perror(f"OpenWeatherMap: Fehler beim Parsen: {e}")
- return None
- def _parse_forecast(self, forecast_data: dict) -> list[ForecastDay]:
- """
- Parst die 5-Tage/3-Stunden-Vorhersage zu Tageswerten.
- Aggregiert die 3-Stunden-Intervalle zu Min/Max pro Tag.
- """
- daily: dict[date, dict] = {}
- for entry in forecast_data.get("list", []):
- dt = datetime.fromtimestamp(entry.get("dt", 0))
- day = dt.date()
- main = entry.get("main", {})
- wind = entry.get("wind", {})
- weather = entry.get("weather", [{}])[0]
- rain = entry.get("rain", {})
- temp = main.get("temp", 0.0)
- wind_speed = wind.get("speed", 0.0) * 3.6
- precip = rain.get("3h", 0.0)
- if day not in daily:
- daily[day] = {
- "temp_min": temp,
- "temp_max": temp,
- "precipitation": 0.0,
- "wind_speed_max": wind_speed,
- "description": weather.get("description", "").capitalize(),
- }
- else:
- d = daily[day]
- d["temp_min"] = min(d["temp_min"], temp)
- d["temp_max"] = max(d["temp_max"], temp)
- d["precipitation"] += precip
- d["wind_speed_max"] = max(d["wind_speed_max"], wind_speed)
- forecast: list[ForecastDay] = []
- for day_date in sorted(daily.keys()):
- d = daily[day_date]
- forecast.append(ForecastDay(
- date=day_date,
- temp_min=d["temp_min"],
- temp_max=d["temp_max"],
- description=d["description"],
- precipitation=round(d["precipitation"], 1),
- wind_speed_max=round(d["wind_speed_max"], 1),
- ))
- return forecast
|