Automatización IoT

Monitorización de baterías domésticas con MQTT: estado de carga, tensiones de celda y alertas de batería baja

Publica datos del BMS como el estado de carga, la tensión del pack, la corriente, la dispersión de tensiones de celda y la temperatura en un broker MQTT, y añade alertas de batería baja y de desequilibrio con histéresis para enterarte de los problemas antes de que el inversor se apague.

Banco de baterías de litio montado en la pared con lecturas del BMS de estado de carga y tensiones de celda mostradas en un panel de monitorización de baterías domésticas
Pruébalo en tu propio broker MQTTConsigue un broker MQTT privado y gestionado con el plan Free y empieza con ejemplos de código listos para usar.
Pruébalo gratis Ejemplos de código Sin tarjeta

La monitorización de baterías domésticas te dice cuánta energía tienes almacenada, cuánto está trabajando la batería y si alguna celda se está desalineando mucho antes de que se convierta en una avería. En este tutorial publicarás datos del sistema de gestión de baterías (BMS), como el estado de carga, la tensión del pack, la corriente, las tensiones de celda y la temperatura, en un broker MQTT, usando como ejemplo práctico un sistema Victron con Venus OS. También añadirás alertas de batería baja y de desequilibrio de celdas que no inunden tu teléfono.

Qué monitorizar en una batería doméstica

Un BMS expone decenas de valores. Estos son los que importan para el funcionamiento diario y la detección temprana de fallos:

Valor Unidad Por qué importa Idea de alerta típica
Estado de carga (SoC) % Energía disponible para la tarde y la noche Baja por debajo del 20 %, se borra por encima del 25 %
Tensión del pack V Comprobación cruzada del SoC, detecta fallos de sensor Fuera del rango normal de la química
Corriente A Positiva cargando, negativa descargando Corriente sostenida cerca del límite del BMS
Potencia W Tensión por corriente; fácil de graficar junto a FV y red Informativo
Tensión de celda mín. / máx. V Celda más débil y más fuerte Dispersión superior a unos 50 mV en reposo
Temperatura °C Las baterías de litio no deben cargarse bajo cero Por debajo de 5 °C o por encima de 45 °C
Alarmas y estado indicadores Eventos de protección del BMS Cualquier alarma activa

En los packs de litio-ferrofosfato (LiFePO4), la dispersión de tensiones de celda suele ser la señal a largo plazo más informativa. El SoC lo estima el BMS y puede derivar; las tensiones de celda se miden. Una dispersión que crece mes a mes apunta a un problema de celda o de balanceo.

De dónde proceden los datos

Cómo obtengas los datos del BMS depende del sistema:

  1. Sistemas Victron con un dispositivo GX (Venus OS). El dispositivo GX ejecuta un broker MQTT local que expone todos los valores del D-Bus interno. Actívalo en Settings > Services (llamado Integrations en el firmware más reciente) con la opción "MQTT on LAN".
  2. Los inversores híbridos suelen exponer el SoC, la tensión y la corriente de la batería mediante registros Modbus, igual que exponen los datos fotovoltaicos; consulta monitorización del inversor solar para el enfoque Modbus.
  3. Los packs caseros con placas BMS JK, JBD o Daly ofrecen interfaces Bluetooth o UART. Proyectos de firmware para ESP32 (incluidos componentes de ESPHome) las leen y publican en MQTT.
  4. Las baterías de rack de servidor con puertos CAN o RS485 suelen informar al inversor, que pasa entonces a ser tu fuente de datos.

El resto de esta guía usa Venus OS porque su interfaz MQTT está bien documentada, pero la lógica de publicación y de alertas sirve para cualquier fuente.

Cómo funciona MQTT en Venus OS

El dispositivo GX publica valores en topics con la forma N/<portal-id>/<service>/<instance>/<path>, con un payload JSON como {"value": 87.5}. Los valores de la batería se encuentran bajo el servicio battery:

N/<portal-id>/battery/512/Soc
N/<portal-id>/battery/512/Dc/0/Voltage
N/<portal-id>/battery/512/Dc/0/Current
N/<portal-id>/battery/512/Dc/0/Power
N/<portal-id>/battery/512/Dc/0/Temperature
N/<portal-id>/battery/512/System/MinCellVoltage
N/<portal-id>/battery/512/System/MaxCellVoltage

El número de instancia (aquí 512) varía según la instalación, y las rutas de tensión de celda solo están disponibles cuando el BMS las comunica. Hay dos detalles que pillan a la gente desprevenida. Primero, el dispositivo GX solo sigue publicando mientras un cliente envíe periódicamente una petición de keepalive a R/<portal-id>/keepalive; sin ella, las actualizaciones se detienen al cabo de un minuto aproximadamente. Segundo, el portal ID aparece en el topic de N/<portal-id>/system/0/Serial, que es como un script puede descubrirlo.

Llevar los datos de la batería a tu broker

El siguiente script se conecta al dispositivo GX en la red local, envía keepalives, recoge los valores de la batería y publica un resumen JSON compacto cada 10 segundos en tu propio broker. Se prueba contra el área pública de broker.connectmq.com, el servidor MQTT público gratuito, que no necesita cuenta pero que cualquiera puede leer: úsalo solo para pruebas, con un prefijo aleatorio.

pip install "paho-mqtt>=2.0"
import json
import threading
import time
import uuid
import paho.mqtt.client as mqtt

GX_HOST = "192.168.1.70"            # dispositivo GX con Venus OS
PREFIX = f"connectmq/demo/{uuid.uuid4().hex[:8]}/battery/house"
PUBLISH_EVERY = 10

PATHS = {
    "Soc": "soc_pct",
    "Dc/0/Voltage": "voltage_v",
    "Dc/0/Current": "current_a",
    "Dc/0/Power": "power_w",
    "Dc/0/Temperature": "temp_c",
    "System/MinCellVoltage": "cell_min_v",
    "System/MaxCellVoltage": "cell_max_v",
}

values = {}
portal = {"id": None}

dest = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id=f"batt-pub-{uuid.uuid4().hex[:6]}")
dest.will_set(f"{PREFIX}/status", "offline", qos=1, retain=True)
dest.connect("broker.connectmq.com", 1883, keepalive=60)
dest.loop_start()
dest.publish(f"{PREFIX}/status", "online", qos=1, retain=True)


def gx_connect(client, userdata, flags, reason_code, properties):
    print("Connected to GX:", reason_code)
    client.subscribe("N/+/system/0/Serial")
    client.subscribe("N/+/battery/+/#")


def gx_message(client, userdata, msg):
    parts = msg.topic.split("/")
    if parts[2] == "system" and parts[-1] == "Serial" and portal["id"] is None:
        portal["id"] = parts[1]
        print("Portal ID:", portal["id"])
        client.publish(f"R/{portal['id']}/keepalive", "")
        return
    if parts[2] != "battery":
        return
    path = "/".join(parts[4:])
    if path in PATHS:
        try:
            values[PATHS[path]] = json.loads(msg.payload).get("value")
        except (ValueError, AttributeError):
            pass


def keepalive_loop(client):
    while True:
        if portal["id"]:
            client.publish(f"R/{portal['id']}/keepalive", "")
        time.sleep(30)


gx = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id=f"batt-gx-{uuid.uuid4().hex[:6]}")
gx.on_connect = gx_connect
gx.on_message = gx_message
gx.connect(GX_HOST, 1883, keepalive=60)
gx.loop_start()
threading.Thread(target=keepalive_loop, args=(gx,), daemon=True).start()

print("Publishing to", PREFIX)
while True:
    time.sleep(PUBLISH_EVERY)
    if not values:
        continue
    out = dict(values)
    if out.get("cell_min_v") is not None and out.get("cell_max_v") is not None:
        out["cell_spread_mv"] = round((out["cell_max_v"] - out["cell_min_v"]) * 1000)
    out["ts"] = int(time.time())
    dest.publish(f"{PREFIX}/telemetry", json.dumps(out), qos=1, retain=True)
    print(out)

Observa el resultado:

mosquitto_sub -h broker.connectmq.com -p 1883 -i batt-view-3318 -t "connectmq/demo/+/battery/#" -v
{"soc_pct": 63.4, "voltage_v": 52.71, "current_a": -18.2, "power_w": -959,
 "temp_c": 21.0, "cell_min_v": 3.291, "cell_max_v": 3.304, "cell_spread_mv": 13, "ts": 1760611200}

Una corriente y una potencia negativas indican que la batería se está descargando. Mantén ese criterio de signos coherente entre fuentes para que los paneles y las automatizaciones no necesiten casos especiales.

Alertas de monitorización de baterías domésticas sin ruido

Una alerta de batería baja que salta cada diez segundos mientras el SoC ronda el umbral acostumbra a la gente a ignorarla. Usa histéresis (umbrales distintos para activar y borrar) y publica las transiciones de las alertas, no cada lectura:

import json
import paho.mqtt.client as mqtt

TELEMETRY = "connectmq/demo/<your-id>/battery/house/telemetry"
ALERTS = "connectmq/demo/<your-id>/battery/house/alerts"

RULES = {
    # nombre: (campo, activar_si, borrar_si)
    "low_soc": ("soc_pct", lambda v: v < 20, lambda v: v > 25),
    "cell_imbalance": ("cell_spread_mv", lambda v: v > 50, lambda v: v < 30),
    "cold_battery": ("temp_c", lambda v: v < 5, lambda v: v > 8),
    "hot_battery": ("temp_c", lambda v: v > 45, lambda v: v < 40),
}
active = set()


def on_connect(client, userdata, flags, reason_code, properties):
    client.subscribe(TELEMETRY, qos=1)


def on_message(client, userdata, msg):
    data = json.loads(msg.payload)
    for name, (field, raise_if, clear_if) in RULES.items():
        v = data.get(field)
        if v is None:
            continue
        if name not in active and raise_if(v):
            active.add(name)
            client.publish(ALERTS, json.dumps({"alert": name, "state": "raised", field: v}), qos=1)
        elif name in active and clear_if(v):
            active.discard(name)
            client.publish(ALERTS, json.dumps({"alert": name, "state": "cleared", field: v}), qos=1)


c = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id="batt-alerts-9902")
c.on_connect = on_connect
c.on_message = on_message
c.connect("broker.connectmq.com", 1883, keepalive=60)
c.loop_forever()

Cualquier cosa capaz de enviar notificaciones, como Node-RED o Home Assistant, puede suscribirse al topic de alertas y reenviarlas a tu teléfono. Para umbrales simples de un solo valor, el portal de ConnectMQ también ofrece reglas de alerta por umbral con un registro de alertas, que cubren "SoC por debajo de 20" sin ejecutar tu propio script; la lógica de histéresis anterior es lo que añades cuando la necesitas.

Mide la dispersión de celdas en reposo o con poca carga. Durante cargas o descargas intensas, y cerca del final de la carga, las celdas LiFePO4 divergen más de forma natural, así que una alerta de desequilibrio en esos periodos no indica necesariamente una avería.

Graficar el comportamiento de la batería a lo largo del tiempo

Los valores en directo responden a "cuánta carga tiene ahora la batería"; el historial responde a "sigue sana la batería". Como el broker solo conserva el último mensaje retenido, almacena la telemetría con un suscriptor como Telegraf escribiendo en InfluxDB, el recorder de Home Assistant o un pequeño script que escriba en SQLite. Tres gráficos compensan rápidamente:

  • SoC con la potencia FV y de red en el mismo eje temporal, que muestra si la batería está bien dimensionada para tu consumo vespertino.
  • SoC mínimo diario, que revela déficits estacionales y si una reserva configurada es demasiado alta o demasiado baja.
  • Dispersión de celdas en reposo a lo largo de los meses, el indicador de salud a largo plazo de los packs LiFePO4. Una tendencia ascendente lenta es la señal para revisar los ajustes de balanceo o contactar con el proveedor.

Lista de comprobación para la resolución de problemas

  1. ¿No llegan mensajes del dispositivo GX? Confirma que MQTT on LAN está activado y que te suscribiste después de conectarte.
  2. ¿Los valores se detienen al cabo de un minuto? El keepalive no llega a R/<portal-id>/keepalive; comprueba el portal ID descubierto.
  3. ¿Faltan las tensiones de celda? Puede que tu BMS no las comunique a Venus OS; revisa la página del dispositivo de la batería en el GX.
  4. ¿El SoC salta tras una carga completa? El BMS ha resincronizado su estimación; es normal tras periodos sin llegar a carga completa.
  5. ¿Los valores parecen desactualizados en el panel? Comprueba el topic de estado retenido; si indica offline, el puente se ha detenido.

Paso a un broker privado

Los datos de la batería revelan cuándo estás en casa y cómo está configurado tu sistema, y el servidor público es compartido, así que usa un broker privado para la instalación permanente. Un broker MQTT gestionado como ConnectMQ permite que el puente de tu Raspberry Pi establezca una única conexión TLS saliente, sin abrir puertos en el router. Crea un broker en el portal de ConnectMQ y cambia la conexión de destino:

dest = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id="YOUR_CLIENT_ID")
dest.username_pw_set("YOUR_USERNAME", "YOUR_PASSWORD")
dest.tls_set()
dest.connect("broker.connectmq.com", 8883, keepalive=60)

A partir de ahí, los datos de la batería pueden guiar decisiones junto a tu monitor de energía doméstico y tu cargador de vehículo eléctrico, como cargar el coche solo cuando la batería supere un SoC determinado. Para las opciones de alojamiento del broker, consulta la guía del broker MQTT en la nube, y para una visión más amplia, la guía de automatización IoT.

Monitoriza tu batería con ConnectMQ

ConnectMQ ofrece un broker MQTT protegido con TLS, con credenciales generadas, un explorador de topics en directo y reglas de alerta por umbral para valores como el estado de carga. Crea un broker Serverless gratuito en app.connectmq.com, sin tarjeta de crédito, y conecta tu puente de batería en minutos.

Preguntas frecuentes

¿Cuál es un buen umbral de batería baja para baterías domésticas?

Muchos usuarios de LiFePO4 activan una alerta en torno al 15 o 20 por ciento de SoC y la borran unos puntos más arriba. Sigue la recomendación del fabricante de tu batería sobre el SoC mínimo para el uso diario.

¿Por qué monitorizar las tensiones de celda y no solo el estado de carga?

El SoC es una estimación que puede derivar, mientras que las tensiones de celda son mediciones directas. Una dispersión creciente entre la celda más alta y la más baja es una señal temprana de problemas de balanceo o de una celda débil.

¿Venus OS necesita internet para MQTT?

No. MQTT on LAN funciona de forma totalmente local. El puente de esta guía lee localmente y solo establece una conexión saliente con el broker que elijas.

¿Puedo monitorizar una batería casera sin hardware Victron?

Sí. Las placas BMS JK, JBD y Daly pueden leerse por Bluetooth o UART con firmware para ESP32, como los componentes de ESPHome, que publican directamente en MQTT usando el mismo diseño de topics y alertas.

Artículos relacionados

Guía para configurar Zigbee2MQTT: del coordinador al panel

Instala Zigbee2MQTT con Docker, configura el coordinador y la conexión MQTT, empareja y renombra dispositivos, contrólalos por MQTT y consulta sus estados en un panel, probando primero en un servidor MQTT público gratuito.

8 min de lectura

Todos los artículos →

Tu broker MQTT privado, a unos pocos clics

Crea una cuenta, obtén las credenciales de tu broker y conecta tu primer dispositivo, sin servidores que instalar, parchear ni escalar.