Mi estación meteorológica no tiene API local, así que la saco de Weather Underground

Una estación 7 en 1 de AliExpress que no abre ningún puerto: cómo leo sus datos desde la Pi con la API de Weather Underground y los llevo a n8n y Home Assistant.

IvánPublicado el 7 minRead in English

Me compré una estación meteorológica 7 en 1 en AliExpress (temperatura, humedad, viento, dirección, lluvia, presión y radiación/UV) con una pantalla a color en el salón. La idea era clara: que la Raspberry Pi tuviera los datos para meterlos en el resumen diario y poder preguntarle al agente de casa si llueve. El problema es que la estación no tiene API local. Te cuento cómo lo he resuelto y la tarde que perdí antes por suponer lo que no era.

1. Primero, averigua qué tienes de verdad

La mía se vende con varias marcas (la misma estación sale con otro logo en otras tiendas). Encontré su IP en la lista del router desenchufándola y viendo qué línea desaparecía, el truco que conté en cómo fijar la IP con una reserva DHCP. El fabricante del chip wifi que salía en la MAC suena a los típicos enchufes inteligentes, y los anuncios hablaban de “app”, así que di por hecho que era un aparato Tuya.

Error. Me pasé la tarde creando un proyecto en la plataforma de desarrolladores de Tuya para nada: la estación no usa Tuya ni ninguna app. Lo que debería haber hecho primero:

# ¿Abre algún puerto? (la IP es de ejemplo)
nmap -p- --open 192.168.1.70

No sale nada. No escucha en ningún puerto. Su página de configuración solo existe en modo punto de acceso: la estación crea su propia red wifi, te conectas a ella y entras en http://192.168.4.1. Ahí hay tres cosas: la wifi de casa, Weather Underground y WeatherCloud. No hay opción de “servidor personalizado”, que en otras estaciones permite mandar los datos a tu propio servidor.

Antes de montar ninguna integración, busca el modelo exacto y su manual. El chip o la marca de la caja no te dicen qué protocolo habla.

2. Darla de alta en Weather Underground

Si los datos solo pueden salir hacia la nube, los saco de la nube. Weather Underground (WU) tiene una API gratuita para el dueño de una estación personal (PWS).

  1. Créate una cuenta en wunderground.com y añade un dispositivo (yo elegí el tipo Other). Te da un Station ID (del estilo ITUPUEBLO12) y una clave de estación.
  2. Pon la estación en modo AP, entra en 192.168.4.1 y mete ese ID y esa clave en el apartado de Weather Underground.
  3. Espera unos minutos: en la página de tu estación en WU tienen que empezar a salir datos.
  4. Ahora sí, en tu perfil, pestaña API Keys, genera la clave.

El paso 4 me tuvo un día parado: WU no te deja crear la API key hasta que tu estación está enviando datos. Yo la di de alta, vi que seguía “sin datos” y lo dejé para el día siguiente. Al configurar la estación, la clave se generó a la primera.

De paso la di de alta también en WeatherCloud: la estación sube a los dos a la vez y así tengo otra web con gráficas. La Pi solo lee de WU.

3. Leerla desde la Pi con Python

El endpoint que interesa es el de la observación actual:

https://api.weather.com/v2/pws/observations/current?stationId=TU_ID&format=json&units=m&numericPrecision=decimal&apiKey=TU_API_KEY

units=m lo da en métrico y numericPrecision=decimal evita que te redondee la temperatura a enteros. Las credenciales van en un JSON con permisos 600, fuera del código:

mkdir -p ~/.config/wunderground
cat > ~/.config/wunderground/credenciales.json <<'EOF'
{"station_id": "TU_ID", "api_key": "TU_API_KEY"}
EOF
chmod 600 ~/.config/wunderground/credenciales.json

El script es solo de librería estándar (nada de pip install). Esta es la parte importante:

CACHE_SEG = 300       # la estación sube cada pocos minutos: no gastes cuota
SIN_DATOS_SEG = 1800  # observación de más de 30 min = la estación no está subiendo

def _leer():
    c = json.loads(CRED.read_text())
    q = urllib.parse.urlencode({"stationId": c["station_id"], "format": "json", "units": "m",
                                "numericPrecision": "decimal", "apiKey": c["api_key"]})
    with urllib.request.urlopen(f"{URL}?{q}", timeout=10) as r:
        if r.status == 204:
            return {"disponible": False, "error": "WU aún no tiene datos de la estación"}
        o = json.load(r)["observations"][0]
    m = o.get("metric") or {}
    edad = time.time() - o["epoch"]
    return {
        "disponible": edad < SIN_DATOS_SEG,
        "temperatura_c": m.get("temp"),
        "humedad_pct": o.get("humidity"),
        "viento_kmh": m.get("windSpeed"),
        "racha_kmh": m.get("windGust"),
        "viento_dir": _rumbo(o.get("winddir")),
        "lluvia_hoy_mm": m.get("precipTotal"),
        "presion_hpa": m.get("pressure"),
        "uv": o.get("uv"),
        # ... sensación, punto de rocío, radiación
    }

Tres detalles que aprendí por el camino:

  • La API responde 204 (sin contenido) si la estación todavía no ha mandado nada. Si no lo tratas, json.load revienta con un error que no dice nada.
  • Devuelve la última observación aunque tenga horas. Por eso miro epoch y marco disponible: false si tiene más de 30 minutos. Si no, el resumen de la mañana te da la temperatura de anoche sin avisar.
  • La dirección del viento viene en grados. La paso a rumbos (N, NNE, NE…) dividiendo entre 22,5, que es lo que se entiende al leerlo en el móvil.

Además, cualquier error (red, clave mal puesta) se devuelve como {"disponible": false, "error": "..."} en vez de lanzar una excepción: el panel no se tiene que caer porque la estación esté sin wifi.

4. Una sola ruta para todo: /api/tiempo

El script se engancha al servidor de mi centro de mando como una ruta más:

if path == "/api/tiempo":
    return self._send(200, tiempo("fresh=1" in self.path))

Y esto es lo que devuelve (valores de ejemplo):

curl -s http://192.168.1.50:8090/api/tiempo
{"disponible": true, "sin_datos_min": 2, "temperatura_c": 22.4, "sensacion_c": 22.4,
 "humedad_pct": 55.0, "viento_kmh": 6.1, "racha_kmh": 11.2, "viento_dir": "SO",
 "lluvia_hoy_mm": 0.0, "presion_hpa": 1016.2, "radiacion_wm2": 310.5, "uv": 3.0}

La gracia de tener una sola ruta es que todo lo demás (n8n, Home Assistant, lo que venga) lee de la Pi y no de WU: la clave está en un solo sitio y la caché de 5 minutos protege la cuota aunque pregunten tres programas a la vez.

5. En el resumen diario y en el agente de n8n

En n8n tengo el flujo del resumen diario y un agente al que pregunto por Telegram (lo conté en volví a n8n). Meter el tiempo fue poco trabajo:

  • Resumen diario: un nodo HTTP Request a /api/tiempo, con On Error: Continue, y un apartado “Tiempo en casa” en el mensaje. Si la estación falla, el resumen llega igual, sin ese apartado.
  • Agente: una herramienta tiempo (nodo HTTP Request Tool) con una descripción que le dice qué devuelve y, sobre todo, lo que no hace:
Tiempo actual medido por la estación meteorológica de casa: temperatura y sensación (°C),
humedad, viento y rachas (km/h) con dirección, lluvia de hoy (mm), presión, radiación y UV.
Si disponible=false la estación no está enviando datos. No da previsión, solo lo que mide ahora.

Esa última frase importa. Un modelo al que le preguntas “¿va a llover mañana?” y que tiene a mano una herramienta llamada tiempo tiende a contestar con lo que mide ahora como si fuera una previsión. En las instrucciones del agente le repito lo mismo: si le preguntan por mañana, que diga que la estación no da previsión.

6. Bonus: Home Assistant con la integración REST

En Home Assistant no hay integración para esta estación, pero no hace falta: la integración rest lee la misma ruta y crea un sensor por campo.

rest:
  - resource: http://192.168.1.50:8090/api/tiempo
    scan_interval: 300
    sensor:
      - name: "Estación temperatura"
        value_template: "{{ value_json.temperatura_c }}"
        unit_of_measurement: "°C"
        device_class: temperature
        availability: "{{ value_json.disponible }}"
      - name: "Estación humedad"
        value_template: "{{ value_json.humedad_pct }}"
        unit_of_measurement: "%"
        device_class: humidity
        availability: "{{ value_json.disponible }}"
      # ... viento, lluvia, presión, UV

availability hace que, si la observación es vieja, los sensores salgan como no disponibles en vez de mostrar un valor congelado. Una trampa con la que me encontré: mi Home Assistant estaba en sistema imperial porque me equivoqué en el asistente inicial, y convertía los °C a °F. Se cambia en Ajustes → Sistema → General → Sistema de unidades.

Lo que no hay que hacer

  • Suponer el protocolo por el chip o por la tienda. Mira los puertos y el manual antes de nada.
  • Poner la API key en el código o en un repo. Va en un fichero aparte con permisos 600.
  • Llamar a WU desde cada programa por separado. Una ruta en la Pi con caché y todos leen de ahí.
  • Fiarte de que la API te da un dato actual. Comprueba la hora de la observación.
  • Dejar que un fallo de la estación tumbe el resumen o el panel. Errores como datos (disponible: false), no como excepciones.

Resumen

  1. Si la estación no abre puertos ni tiene servidor personalizado, sube a Weather Underground y lee de su API.
  2. La API key solo se genera cuando la estación ya está enviando datos.
  3. Un script de librería estándar con caché de 5 minutos, control del 204 y de la edad de la observación.
  4. Una sola ruta /api/tiempo en la Pi para n8n, el agente y Home Assistant.

Depender de la nube para algo que está a cinco metros no me encanta, pero para lo que costó la estación, es un precio razonable. Si algún día compro otra, la primera pregunta será si deja mandar los datos a un servidor propio.

Mini-quiz

¿Te has quedado con la idea?

Tres preguntas rápidas. Cada acierto suma 10 XP.

  1. ¿Por qué la Pi no puede preguntarle directamente a la estación por sus datos?
  2. Al dar de alta la estación en Weather Underground, la pestaña de API keys no te deja crear la clave. ¿Qué pasa?
  3. ¿Para qué sirve comprobar la edad de la última observación (más de 30 minutos = no disponible)?
  • #estacion-meteorologica
  • #weather-underground
  • #raspberry-pi
  • #n8n
  • #home-assistant
Esc