- Identité noir & blanc pur (zéro gris, anti-ghosting e-ink) ; hachures pour conso/alarme - Typo vendorisée : Archivo (mots) + JetBrains Mono (nombres tabulaires), @font-face base64 - Jauge signature : noir = restant, repère seuil 20 %, hachures sous le seuil - Météo : glyphes 1-bit en silhouette (weather.kind) au lieu d'emoji couleur - Layout rééquilibré (plus de débordement), états dégradés soignés - dev/preview.py : aperçu hors-ligne du template
88 lines
2.6 KiB
Python
88 lines
2.6 KiB
Python
"""Météo via Open-Meteo (gratuit, sans clé API)."""
|
|
from __future__ import annotations
|
|
|
|
from dataclasses import dataclass
|
|
|
|
import httpx
|
|
|
|
from config import config
|
|
|
|
API_URL = "https://api.open-meteo.com/v1/forecast"
|
|
|
|
# Codes WMO -> (libellé court FR, kind). `kind` pilote le glyphe 1-bit dessiné côté
|
|
# template (clear/partly/cloudy/fog/rain/snow/storm) : pas d'emoji couleur sur e-ink.
|
|
WMO = {
|
|
0: ("Dégagé", "clear"),
|
|
1: ("Peu nuageux", "partly"),
|
|
2: ("Nuageux", "partly"),
|
|
3: ("Couvert", "cloudy"),
|
|
45: ("Brouillard", "fog"),
|
|
48: ("Brouillard givrant", "fog"),
|
|
51: ("Bruine légère", "rain"),
|
|
53: ("Bruine", "rain"),
|
|
55: ("Bruine forte", "rain"),
|
|
61: ("Pluie faible", "rain"),
|
|
63: ("Pluie", "rain"),
|
|
65: ("Pluie forte", "rain"),
|
|
71: ("Neige faible", "snow"),
|
|
73: ("Neige", "snow"),
|
|
75: ("Neige forte", "snow"),
|
|
80: ("Averses", "rain"),
|
|
81: ("Averses", "rain"),
|
|
82: ("Fortes averses", "storm"),
|
|
95: ("Orage", "storm"),
|
|
96: ("Orage + grêle", "storm"),
|
|
99: ("Orage + grêle", "storm"),
|
|
}
|
|
|
|
|
|
@dataclass
|
|
class Weather:
|
|
ok: bool
|
|
error: str | None = None
|
|
temp: float | None = None
|
|
feels_like: float | None = None
|
|
label: str = ""
|
|
kind: str = ""
|
|
temp_min: float | None = None
|
|
temp_max: float | None = None
|
|
precip_prob: int | None = None
|
|
|
|
|
|
async def fetch_weather() -> Weather:
|
|
params = {
|
|
"latitude": config.weather_lat,
|
|
"longitude": config.weather_lon,
|
|
"current": "temperature_2m,apparent_temperature,weather_code",
|
|
"daily": "temperature_2m_max,temperature_2m_min,precipitation_probability_max",
|
|
"timezone": config.timezone,
|
|
"forecast_days": 1,
|
|
}
|
|
try:
|
|
async with httpx.AsyncClient(timeout=15) as client:
|
|
resp = await client.get(API_URL, params=params)
|
|
resp.raise_for_status()
|
|
data = resp.json()
|
|
except httpx.HTTPError as exc:
|
|
return Weather(ok=False, error=f"réseau: {exc}")
|
|
|
|
cur = data.get("current", {})
|
|
daily = data.get("daily", {})
|
|
code = int(cur.get("weather_code", -1))
|
|
label, kind = WMO.get(code, ("—", "cloudy"))
|
|
|
|
def _first(key: str):
|
|
vals = daily.get(key) or []
|
|
return vals[0] if vals else None
|
|
|
|
return Weather(
|
|
ok=True,
|
|
temp=cur.get("temperature_2m"),
|
|
feels_like=cur.get("apparent_temperature"),
|
|
label=label,
|
|
kind=kind,
|
|
temp_min=_first("temperature_2m_min"),
|
|
temp_max=_first("temperature_2m_max"),
|
|
precip_prob=_first("precipitation_probability_max"),
|
|
)
|