Files
Home_assistant/backend/app/homelab/openmeteo.py
T
2026-06-10 14:56:18 +03:00

154 lines
6.0 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import time
from typing import Any
import httpx
from app.config import get_settings
WEATHER_CODES: dict[int, str] = {
0: "ясно",
1: "преимущественно ясно",
2: "переменная облачность",
3: "пасмурно",
45: "туман",
48: "изморозь",
51: "морось",
53: "морось",
55: "морось",
61: "дождь",
63: "дождь",
65: "сильный дождь",
71: "снег",
73: "снег",
75: "сильный снег",
80: "ливень",
81: "ливень",
82: "сильный ливень",
95: "гроза",
96: "гроза с градом",
99: "гроза с градом",
}
_cache: dict[str, Any] = {"data": None, "expires_at": 0.0}
class OpenMeteoClient:
def __init__(self) -> None:
settings = get_settings()
self.base_url = settings.openmeteo_base_url.rstrip("/")
self.lat = settings.weather_lat
self.lon = settings.weather_lon
self.location_name = settings.weather_location_name
self.cache_ttl = settings.weather_cache_sec
def _fetch_raw(self) -> dict[str, Any]:
now = time.time()
if _cache["data"] and now < _cache["expires_at"]:
return _cache["data"]
params = {
"latitude": self.lat,
"longitude": self.lon,
"current": (
"temperature_2m,apparent_temperature,relative_humidity_2m,"
"precipitation,weather_code,wind_speed_10m"
),
"hourly": "temperature_2m,precipitation_probability,precipitation,weather_code",
"timezone": "auto",
"forecast_days": 2,
}
with httpx.Client(timeout=15.0) as client:
response = client.get(f"{self.base_url}/v1/forecast", params=params)
response.raise_for_status()
data = response.json()
_cache["data"] = data
_cache["expires_at"] = now + self.cache_ttl
return data
def fetch_current_and_hourly(self, hours_ahead: int = 12) -> dict[str, Any]:
try:
raw = self._fetch_raw()
except Exception as exc:
return {"ok": False, "error": str(exc), "location": self.location_name}
current = raw.get("current") or {}
hourly = raw.get("hourly") or {}
times = hourly.get("time") or []
limit = min(hours_ahead, len(times))
hourly_slice = []
for i in range(limit):
hourly_slice.append({
"time": times[i],
"temperature_c": hourly.get("temperature_2m", [None])[i],
"precipitation_mm": hourly.get("precipitation", [None])[i],
"precipitation_probability": hourly.get("precipitation_probability", [None])[i],
"weather_code": hourly.get("weather_code", [None])[i],
})
code = current.get("weather_code")
return {
"ok": True,
"location": self.location_name,
"current": {
"time": current.get("time"),
"temperature_c": current.get("temperature_2m"),
"apparent_temperature_c": current.get("apparent_temperature"),
"humidity_pct": current.get("relative_humidity_2m"),
"precipitation_mm": current.get("precipitation"),
"wind_speed_kmh": current.get("wind_speed_10m"),
"weather_code": code,
"conditions": WEATHER_CODES.get(code, "неизвестно") if code is not None else "неизвестно",
},
"hourly": hourly_slice,
}
def rain_summary(self, hours_ahead: int = 12) -> str:
data = self.fetch_current_and_hourly(hours_ahead=hours_ahead)
if not data.get("ok"):
return f"Погода недоступна: {data.get('error', 'ошибка')}"
rainy_hours = []
for hour in data.get("hourly") or []:
prob = hour.get("precipitation_probability")
precip = hour.get("precipitation_mm") or 0
if (prob is not None and prob >= 40) or precip > 0:
time_str = (hour.get("time") or "")[11:16]
rainy_hours.append(f"{time_str} ({prob}% вероятность, {precip} мм)")
if rainy_hours:
return "Ожидаются осадки: " + ", ".join(rainy_hours[:6])
return "Существенных осадков в ближайшие часы не ожидается."
def format_weather_snapshot(data: dict[str, Any] | None = None) -> str:
client = OpenMeteoClient()
snapshot = data if data is not None else client.fetch_current_and_hourly(hours_ahead=6)
lines = ["[Погода]"]
if not snapshot.get("ok"):
lines.append(f"Данные недоступны ({snapshot.get('error', 'ошибка')}).")
lines.append("Для точного ответа вызови get_weather.")
return "\n".join(lines)
cur = snapshot.get("current") or {}
lines.append(
f"{snapshot.get('location')}: {cur.get('temperature_c')}°C "
f"(ощущается {cur.get('apparent_temperature_c')}°C), "
f"{cur.get('conditions')}, ветер {cur.get('wind_speed_kmh')} км/ч."
)
hourly = snapshot.get("hourly") or []
rainy_hours = []
for hour in hourly:
prob = hour.get("precipitation_probability")
precip = hour.get("precipitation_mm") or 0
if (prob is not None and prob >= 40) or precip > 0:
time_str = (hour.get("time") or "")[11:16]
rainy_hours.append(f"{time_str} ({prob}% вероятность, {precip} мм)")
if rainy_hours:
lines.append("Ожидаются осадки: " + ", ".join(rainy_hours[:6]))
else:
lines.append("Существенных осадков в ближайшие часы не ожидается.")
lines.append("Вопросы «что на улице» / «будет ли дождь» — get_weather.")
return "\n".join(lines)