| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132 |
- # -*- coding: utf-8 -*-
- """
- Abstrakte Basisklasse fuer Wetter-Provider.
- Stellt die gemeinsame Geocoding-Funktionalitaet bereit
- (Open-Meteo Geocoding API, kostenlos, kein API-Key noetig).
- Nutzt den OnlineService fuer HTTP-Requests mit Offline-Cache.
- """
- from __future__ import annotations
- from abc import ABC, abstractmethod
- from typing import TYPE_CHECKING
- from plugins.weather.models import WeatherResult
- from trixy_core.utils.debug import pdebug, perror
- if TYPE_CHECKING:
- from trixy_core.online.client import HttpClient
- class WeatherProvider(ABC):
- """
- Abstrakte Basisklasse fuer alle Wetter-Provider.
- Jeder Provider muss get_weather() und health_check() implementieren.
- Geocoding ueber die Open-Meteo-API steht allen Providern zur Verfuegung.
- """
- def __init__(self, name: str, config: dict, http: "HttpClient | None" = None) -> None:
- self.name = name
- self.config = config
- self._http = http
- @abstractmethod
- async def get_weather(
- self, lat: float, lon: float, location_name: str = ""
- ) -> WeatherResult | None:
- """
- Ruft aktuelle Wetterdaten und Vorhersage ab.
- Args:
- lat: Breitengrad
- lon: Laengengrad
- location_name: Optionaler Stadtname fuer die Anzeige
- Returns:
- WeatherResult oder None bei Fehler
- """
- ...
- @abstractmethod
- async def health_check(self) -> bool:
- """
- Prueft ob der Provider erreichbar ist.
- Returns:
- True wenn der Provider verfuegbar ist
- """
- ...
- async def geocode(self, city: str) -> tuple[float, float, str] | None:
- """
- Loest einen Stadtnamen in Koordinaten auf.
- Nutzt die kostenlose Open-Meteo Geocoding API.
- Args:
- city: Stadtname (z.B. "Berlin", "Muenchen")
- Returns:
- (lat, lon, aufgeloester_name) oder None bei Fehler
- """
- # Gaengige Umlaut-Umschreibungen zurueckwandeln
- # (STT gibt oft "Muenchen" statt "München")
- _umlaut_map = {
- "ue": "ü", "Ue": "Ü",
- "oe": "ö", "Oe": "Ö",
- "ae": "ä", "Ae": "Ä",
- }
- resolved_city = city
- for ascii_form, umlaut in _umlaut_map.items():
- resolved_city = resolved_city.replace(ascii_form, umlaut)
- url = "https://geocoding-api.open-meteo.com/v1/search"
- params = {
- "name": resolved_city,
- "count": "5",
- "language": "de",
- "format": "json",
- }
- try:
- # OnlineService HTTP-Client nutzen (mit Cache)
- if self._http:
- resp = await self._http.get(url, params=params, cache_ttl=86400)
- if not resp.ok:
- pdebug(f"WeatherProvider: Geocoding HTTP-Fehler: {resp.error or resp.status_code}")
- return None
- data = resp.json()
- else:
- # Fallback: httpx direkt
- import httpx
- async with httpx.AsyncClient(timeout=10.0) as client:
- response = await client.get(url, params=params)
- response.raise_for_status()
- data = response.json()
- results = data.get("results")
- if not results:
- pdebug(f"WeatherProvider: Keine Ergebnisse fuer '{city}'")
- return None
- # Bestes Ergebnis waehlen: exakter Name-Match bevorzugen
- result = results[0]
- for r in results:
- r_name = r.get("name", "").lower()
- if r_name == resolved_city.lower() or r_name == city.lower():
- result = r
- break
- lat = result["latitude"]
- lon = result["longitude"]
- name = result.get("name", city)
- pdebug(f"WeatherProvider: Geocoding '{city}' -> {name} ({lat}, {lon})")
- return (lat, lon, name)
- except Exception as e:
- perror(f"WeatherProvider: Geocoding-Fehler fuer '{city}': {e}")
- return None
|