La monitorización OEE en tiempo real sustituye la hoja de cálculo de fin de turno por un número que se actualiza cada minuto, cuando todavía hay tiempo para actuar. En este tutorial construirás el pipeline completo: las máquinas publican su estado y sus conteos por MQTT, un servicio en Python calcula la disponibilidad, el rendimiento y la calidad del turno en curso, y el resultado se vuelve a publicar en el broker para que cualquier dashboard lo muestre. Puedes ejecutar todas las piezas en un portátil contra un broker real.
Por qué el OEE en tiempo real supera a los informes de fin de turno
Un informe de turno te dice qué salió mal ayer. Para entonces el turno ha terminado, los operarios se han ido a casa y las causas están medio olvidadas. Una cifra de OEE en vivo mostrada en la línea permite al supervisor ver cómo cae la disponibilidad tras una avería, notar que el rendimiento baja cuando se reduce la velocidad y reaccionar mientras las mismas personas y condiciones siguen ahí.
El cálculo automático también elimina las discusiones sobre los datos de entrada. Cuando los estados y conteos llegan directamente de la máquina, el número es el mismo para operarios, supervisores y gerentes, y la conversación pasa de "¿es correcto el número?" a "¿qué hacemos al respecto?".
Arquitectura
[Machine / PLC gateway] --state, counts--> [MQTT broker] --> [OEE service] --oee--> [MQTT broker] --> [Dashboards, andon, historian]
Cada componente habla solo con el broker. Las máquinas no saben que existe el servicio de OEE y los dashboards no saben de dónde vienen los datos. Ese desacoplamiento es la razón principal para usar un broker; si el patrón es nuevo para ti, lee primero qué hace un broker MQTT.
Topics para una máquina:
| Topic | Payload | Publicación |
|---|---|---|
.../line2/filler/state |
{"state": "RUNNING", "ts": ...} |
En cada cambio, retained |
.../line2/filler/counts |
{"total": 1234, "reject": 12, "ts": ...} |
Cada 10 s, acumulado |
.../line2/filler/config |
{"ideal_cycle_s": 1.0, "shift_start": ...} |
Al cambiar, retained |
.../line2/filler/oee |
{"availability": ..., "oee": ...} |
Cada 60 s por el servicio, retained |
Los estados son RUNNING, DOWN, IDLE, SETUP y PLANNED_STOP. Las paradas planificadas se excluyen del tiempo de producción planificado; cualquier otro estado distinto de RUNNING cuenta como pérdida de disponibilidad. Los detalles de la fórmula y los casos límite se explican en cómo calcular el OEE.
Paso 1: simular una máquina
Usa el servidor MQTT público gratuito para el desarrollo; está pensado para pruebas y prototipos, no para datos de producción. Elige un ID aleatorio para el prefijo de tus topics, porque el área pública es visible para todo el mundo.
# machine_sim.py
import json
import random
import time
import paho.mqtt.client as mqtt
BASE = "connectmq/demo/q8n3d1/plant1/line2/filler"
IDEAL_CYCLE_S = 1.0
client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id="filler-sim-q8n3d1")
client.connect("broker.connectmq.com", 1883, keepalive=30)
client.loop_start()
shift_start = time.time()
client.publish(f"{BASE}/config", json.dumps({"ideal_cycle_s": IDEAL_CYCLE_S, "shift_start": shift_start}), qos=1, retain=True)
state, total, reject = "RUNNING", 0, 0
client.publish(f"{BASE}/state", json.dumps({"state": state, "ts": time.time()}), qos=1, retain=True)
while True:
# Cambios de estado aleatorios
if state == "RUNNING" and random.random() < 0.02:
state = random.choice(["DOWN", "IDLE", "SETUP"])
client.publish(f"{BASE}/state", json.dumps({"state": state, "ts": time.time()}), qos=1, retain=True)
elif state != "RUNNING" and random.random() < 0.2:
state = "RUNNING"
client.publish(f"{BASE}/state", json.dumps({"state": state, "ts": time.time()}), qos=1, retain=True)
if state == "RUNNING":
made = int(10 / IDEAL_CYCLE_S * random.uniform(0.8, 0.98))
total += made
reject += sum(1 for _ in range(made) if random.random() < 0.02)
client.publish(f"{BASE}/counts", json.dumps({"total": total, "reject": reject, "ts": time.time()}), qos=1)
time.sleep(10)
Paso 2: crear el servicio de monitorización OEE en tiempo real
El servicio se suscribe con comodines a todas las máquinas de la línea, guarda el historial de estados del turno actual y recalcula cada 60 segundos. Gestiona varias máquinas a la vez, porque el nombre de la máquina se toma del topic.
# oee_service.py
import json
import threading
import time
from collections import defaultdict
import paho.mqtt.client as mqtt
LINE = "connectmq/demo/q8n3d1/plant1/line2"
INTERVAL_S = 60
machines = defaultdict(lambda: {
"events": [], # lista de (ts, state)
"total": 0, "reject": 0,
"ideal_cycle_s": None, "shift_start": None,
})
lock = threading.Lock()
def on_connect(client, userdata, flags, reason_code, properties):
print("Connected:", reason_code)
client.subscribe([(f"{LINE}/+/state", 1), (f"{LINE}/+/counts", 1), (f"{LINE}/+/config", 1)])
def on_message(client, userdata, msg):
parts = msg.topic.split("/")
machine, kind = parts[-2], parts[-1]
data = json.loads(msg.payload)
with lock:
m = machines[machine]
if kind == "config":
m["ideal_cycle_s"] = data["ideal_cycle_s"]
if m["shift_start"] != data["shift_start"]:
m["shift_start"] = data["shift_start"]
# Llevar el estado actual al nuevo turno y descartar el historial anterior
m["events"] = [(data["shift_start"], m["events"][-1][1])] if m["events"] else []
elif kind == "state":
m["events"].append((data["ts"], data["state"]))
elif kind == "counts":
m["total"], m["reject"] = data["total"], data["reject"]
def calculate(m, now):
start = m["shift_start"]
events = sorted(e for e in m["events"] if e[0] <= now)
run = planned_stop = 0.0
for i, (ts, state) in enumerate(events):
seg_start = max(ts, start)
seg_end = events[i + 1][0] if i + 1 < len(events) else now
if seg_end <= seg_start:
continue
if state == "RUNNING":
run += seg_end - seg_start
elif state == "PLANNED_STOP":
planned_stop += seg_end - seg_start
planned = (now - start) - planned_stop
total, reject = m["total"], m["reject"]
a = run / planned if planned > 0 else 0.0
p = min(m["ideal_cycle_s"] * total / run, 1.0) if run > 0 else 0.0
q = (total - reject) / total if total > 0 else 0.0
return {
"availability": round(a, 4), "performance": round(p, 4), "quality": round(q, 4),
"oee": round(a * p * q, 4), "run_min": round(run / 60, 1),
"planned_min": round(planned / 60, 1), "good": total - reject, "ts": now,
}
client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id="oee-service-q8n3d1")
client.on_connect = on_connect
client.on_message = on_message
client.connect("broker.connectmq.com", 1883, keepalive=30)
client.loop_start()
while True:
time.sleep(INTERVAL_S)
now = time.time()
with lock:
ready = {k: v for k, v in machines.items() if v["shift_start"] and v["ideal_cycle_s"]}
results = {k: calculate(v, now) for k, v in ready.items()}
for machine, result in results.items():
client.publish(f"{LINE}/{machine}/oee", json.dumps(result), qos=1, retain=True)
print(machine, result)
Ejecuta el simulador en una terminal y el servicio en otra. Después de un minuto verás los valores de OEE impresos y publicados. Obsérvalos desde una tercera terminal:
mosquitto_sub -h broker.connectmq.com -p 1883 -i oee-viewer-q8n3d1 -t "connectmq/demo/q8n3d1/plant1/line2/+/oee" -v
Cómo resuelve el diseño los problemas del mundo real
Reinicios del servicio. Como el estado y la configuración son retained, un servicio reiniciado recibe de inmediato el estado actual y el inicio de turno de cada máquina. Pierde el historial de cambios de estado anteriores, por lo que la disponibilidad es aproximada hasta el siguiente turno. En producción, guarda los eventos en una pequeña base de datos o reconstrúyelos desde un historian al arrancar.
Mensajes perdidos. Los conteos son acumulados, así que un mensaje de conteo perdido solo retrasa la actualización. Los mensajes de estado usan QoS 1 para que el broker los confirme.
Cambio de turno. El gateway de la máquina (o un MES) publica un nuevo config con el nuevo shift_start. El servicio reinicia automáticamente el historial de esa máquina. Los contadores deberían reiniciarse en el mismo momento.
Desfase de relojes. Todas las marcas de tiempo vienen del edge. Mantén los gateways sincronizados con NTP; de lo contrario, un gateway con el reloj un minuto adelantado producirá duraciones negativas o infladas.
Rendimiento por encima del 100 por ciento. El límite oculta un tiempo de ciclo ideal mal configurado. Registra una advertencia cuando se active y corrige los datos maestros.
Mostrar el OEE en vivo
Cualquier pantalla compatible con MQTT puede suscribirse a los topics oee. Opciones que funcionan bien:
- Node-RED Dashboard 2.0 con un indicador para el OEE y tres barras para D, R y C. La guía del dashboard de OEE en Node-RED lo explica paso a paso.
- Grafana alimentado por Telegraf e InfluxDB para ver el historial de turnos junto al valor en vivo, como en el tutorial del dashboard de monitorización de líneas de producción.
- Una página web que use MQTT.js sobre
wss://broker.connectmq.com:8084/mqtt, sin necesidad de backend.
Muestra los tres factores, no solo el OEE. Un supervisor que ve la disponibilidad al 95 por ciento y el rendimiento al 70 por ciento sabe que debe mirar la velocidad y las microparadas, no las averías. Combina el número en vivo con el seguimiento de paradas de máquina para que las paradas largas reciban un motivo mientras están frescas. Si quieres ir un paso más allá, los mismos topics oee pueden alimentar un gemelo digital 3D de la línea.
Añadir objetivos y alertas
Un número de OEE en vivo es más útil cuando se compara con algo. Vale la pena hacer dos añadidos desde el principio.
Ritmo frente al objetivo del turno. Publica un conteo objetivo para el turno en el topic config y deja que el servicio añada expected_now y gap a cada resultado: el conteo que la línea debería haber alcanzado en ese momento del turno y la diferencia con el real. Los operarios reaccionan mucho más a "vamos 340 unidades por detrás" que a "el OEE está en el 71 por ciento", y ambos números salen de los mismos datos.
Alertas por umbral. Decide qué condiciones requieren que una persona actúe, por ejemplo una disponibilidad inferior al 80 por ciento en la última hora o una máquina en DOWN durante más de diez minutos. Hay dos formas sencillas de implementarlas:
- Haz que el servicio de OEE publique un mensaje
alertcuando se dispare una regla y que un flujo de Node-RED, una luz andon o una integración de mensajería se suscriba a él. - Usa las reglas de alerta por umbral del portal de ConnectMQ sobre los topics
oeepublicados, que registran cada disparo en el log de alertas sin código adicional.
Mantén la lista de reglas corta. Las alertas que saltan varias veces por turno sin que nadie actúe deben ajustarse o eliminarse; de lo contrario, la gente deja de leerlas todas.
Ventanas móviles. Además del valor acumulado del turno, calcula el OEE de los últimos 60 minutos. Los valores acumulados se mueven despacio al final del turno y pueden ocultar un problema reciente; una hora móvil lo muestra de inmediato. La misma función calculate sirve si le pasas como inicio de ventana el momento de hace una hora en lugar del inicio del turno.
Paso a producción
Deja el servidor público antes de conectar máquinas reales. Con ConnectMQ creas un broker privado en el portal y recibes un usuario, una contraseña y un client ID para cada cliente. Cambia la conexión en ambos scripts:
client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id="YOUR_CLIENT_ID")
client.username_pw_set("YOUR_USERNAME", "YOUR_PASSWORD")
client.tls_set() # el almacén de CA del sistema valida el certificado de Let's Encrypt
client.connect("broker.connectmq.com", 8883, keepalive=30)
Un broker MQTT gestionado libera al equipo de planta del mantenimiento del broker, y el explorador de topics en vivo del portal muestra si todas las máquinas están publicando antes de que depures el servicio. Para tener la visión completa, lee la guía de manufactura inteligente.
Ejecuta tu servicio de OEE en ConnectMQ
Crea un broker gratuito en app.connectmq.com con el plan Serverless, sin tarjeta de crédito, apunta tus gateways y el servicio de OEE hacia él y ten el OEE en vivo en la pantalla de la línea hoy mismo.
Preguntas frecuentes
¿Con qué frecuencia debe recalcularse el OEE en tiempo real?
Una vez por minuto es un buen valor por defecto. Es lo bastante ágil para la planta y evita un número que salta con cada mensaje de conteo.
¿Dónde debe ejecutarse el cálculo del OEE?
En un único servicio que publique el resultado en el broker, para que todos los dashboards muestren el mismo valor. Evita recalcularlo en cada dashboard con lógicas ligeramente distintas.
¿Puede gestionar varias máquinas y líneas?
Sí. El servicio usa suscripciones con comodines e indexa todo por el nombre de máquina tomado del topic. Añade un nivel al comodín para cubrir varias líneas.
¿Qué ocurre si una máquina deja de publicar?
Su último estado retained permanece en el broker. Añade un mensaje Last Will en el gateway que publique OFFLINE para que el servicio trate el silencio como desconocido y no como funcionamiento.



