"""Live weather for the farm from the free Open-Meteo forecast API (no key). Cached 30 minutes.""" import json import ssl import time import urllib.request from datetime import datetime import config _cache = {"t": 0, "data": None} URL = ("https://api.open-meteo.com/v1/forecast?latitude={lat}&longitude={lon}" "&hourly=et0_fao_evapotranspiration,precipitation,soil_temperature_0cm,shortwave_radiation,wind_speed_10m" "&past_days=1&forecast_days=2&timezone=Africa%2FCairo") def _ssl_context(): """Use certifi's up-to-date CA bundle when available (old Windows Python installs ship expired CAs).""" try: import certifi return ssl.create_default_context(cafile=certifi.where()) except ImportError: return ssl.create_default_context() def _fetch(): with urllib.request.urlopen(URL.format(lat=config.FARM_LAT, lon=config.FARM_LON), timeout=8, context=_ssl_context()) as r: return json.load(r)["hourly"] def current(): """Return the weather features the irrigation model needs (with safe fallbacks offline).""" if time.time() - _cache["t"] > 1800 or _cache["data"] is None: try: _cache["data"] = _fetch() _cache["t"] = time.time() except Exception: _cache["data"] = None h = _cache["data"] if not h: return {"online": False, "et0_24h": 4.0, "rain_24h": 0.0, "soil_temp": 25.0, "radiation": 400.0, "wind": 10.0, "et0_next_24h": 4.0} now = datetime.now().strftime("%Y-%m-%dT%H:00") i = h["time"].index(now) if now in h["time"] else len(h["time"]) // 2 past = slice(max(0, i - 23), i + 1) nxt = slice(i + 1, i + 25) return { "online": True, "et0_24h": round(sum(v or 0 for v in h["et0_fao_evapotranspiration"][past]), 2), "rain_24h": round(sum(v or 0 for v in h["precipitation"][past]), 2), "soil_temp": h["soil_temperature_0cm"][i], "radiation": h["shortwave_radiation"][i], "wind": h["wind_speed_10m"][i], "et0_next_24h": round(sum(v or 0 for v in h["et0_fao_evapotranspiration"][nxt]), 2), "rain_next_24h": round(sum(v or 0 for v in h["precipitation"][nxt]), 2), }