
ESP32 enviando datos a SCADA y MES
SCADA no quiere tu JSON: quiere tags. Modelado de tópicos, calidad del dato, heartbeat y por qué la marca de tiempo del gateway no basta.
Requisitos de hardware:
- Placa ESP32 ou ESP32-S3
- Um broker MQTT alcançável pela rede da planta
- Um SCADA ou plataforma que consuma MQTT
Descarga el material de este tutorial:
- Projeto completo (ZIP)143 KBplatformio.ini, src/main.cpp comentado, include/config.h com a hierarquia de tópicos, README e o manual em PDF.
- Manual em PDF (5 páginas)171 KBModelagem de tópicos, carimbo de tempo, tabela de qualidade, heartbeat, retenção e checklist de entrega.
¿Quieres recibir nuevos manuales y esquemas industriales? Únete a nuestro canal de Telegram / WhatsApp
El error inicial: pensar que basta con publicar
Hacer que el ESP32 publique en un tópico MQTT lleva diez minutos. Casi todos los tutoriales terminan ahí, y por eso tantas integraciones mueren en la primera reunión con quien opera el SCADA.
SCADA no quiere tu JSON. Trabaja con tags: un punto con nombre, tipo, unidad, rango, calidad y marca de tiempo definidos. El operador espera abrir la pantalla y ver PANEL_03.MOTOR_A.TEMPERATURA con valor, unidad e indicador de que ese número es fiable ahora.
Entregar un JSON con nombres de campos inventados por quien escribió el firmware deja al otro lado el trabajo de adivinar qué significa cada cosa. Ahí se atasca la integración.
Este artículo trata de lo que viene después de que publish funcione.
Modelado de tópicos: el contrato entre ambos lados
La jerarquía del tópico es la primera decisión y la más difícil de cambiar cuando ya está en producción.
Una convención que funciona va de lo general a lo específico, con un nivel fijo por dimensión:
| 1 | planta/area/linea/equipo/magnitud |
| 2 | planta1/molienda/linea2/motor03/temperatura |
| 3 | planta1/molienda/linea2/motor03/corriente |
| 4 | planta1/servicios/compresor01/presion |
Tres reglas evitan retrabajo.
Nunca pongas el valor en el nombre del tópico. Un tópico por magnitud y el valor en la carga. Poner motor03/temperatura/78 crea un tópico nuevo en cada lectura y destruye las suscripciones con comodín.
Usa identificadores estables, no descripciones. El motor 03 seguirá siendo el 03 cuando cambie el modelo. motor-weg-w22-2024 se convierte en mentira en el primer mantenimiento.
Sin acentos, espacios ni mayúsculas mezcladas. El broker lo acepta, pero después se convierte en un problema para cada herramienta consumidora.
La marca de tiempo del gateway no sirve
Este es el punto que separa una integración que pasa una auditoría de otra que no.
SCADA marca el dato cuando llega. Si la red se atasca durante dos minutos, todas las lecturas de ese intervalo llegan juntas con la misma hora y el historial muestra un escalón que nunca existió en el proceso.
Peor aún: si tienes un búfer offline, seis horas de datos guardados llegan con la hora de la reconexión. El gráfico deja de servir justo en el periodo que querías investigar.
La carga debe llevar el instante de la lectura:
| 1 | { |
| 2 | "t": 1755990000, |
| 3 | "v": 78.4, |
| 4 | "q": 192, |
| 5 | "u": "C" |
| 6 | } |
Los campos son cortos porque la carga se repite miles de veces al día y cada byte se multiplica. t es el instante de lectura en segundos desde 1970, v es el valor, q la calidad y u la unidad. Para variables rápidas, publica t en milisegundos y acuerda esa unidad con quien configura el SCADA.
Aquí vuelve el problema del artículo del búfer: el ESP32 no tiene reloj al encender. Sin NTP sincronizado no puede marcar la hora. La salida honesta es publicar calidad incierta mientras el reloj no esté sincronizado; nunca inventar una hora.
Calidad del dato: el campo que nadie rellena
Todo protocolo industrial serio tiene calidad y es el campo que más ignoran quienes vienen del lado web.
La razón es sencilla: cero es un valor válido. Si el sensor se rompe y el firmware publica 0, SCADA muestra cero grados y el operador lo cree. Con calidad, muestra cero marcado como malo, y la diferencia está entre una alarma falsa y una decisión equivocada.
La convención heredada de OPC usa tres rangos:
| 1 | 192 Buena lectura normal, dentro de lo esperado |
| 2 | 64 Incierta valor antiguo, reloj sin NTP, sensor fuera de rango |
| 3 | 0 Mala fallo de lectura, sensor sin respuesta, bus silencioso |
Publicar siempre 192 equivale a no tener el campo. El valor de la calidad está en que pueda cambiar.
Heartbeat: distinguir detenido de muerto
Un valor que no cambia durante diez minutos puede significar dos cosas opuestas: el proceso está estable o el gateway murió.
Sin heartbeat, SCADA no puede saberlo. La respuesta equivocada cuesta en ambos sentidos: alarma falsa cuando todo está bien o silencio cuando la planta se ha detenido.
La solución tiene dos capas.
El Last Will del artículo de MQTT cubre la muerte repentina: el broker publica offline por ti cuando la conexión cae sin despedida. Para que funcione, el Last Will debe ser retenido y publicado en el mismo tópico de estado descrito abajo, así sobrescribe el último "online" que guardó el broker.
Un tópico de estado periódico, publicado cada treinta segundos con retención, cubre el resto:
| 1 | planta1/molienda/linea2/gw01/status |
| 2 | {"t":1755990000,"online":true,"uptime":86400,"rssi":-62,"fw":"1.4.2","reset":"POWERON"} |
El valor reset viene del artículo del watchdog y vale mucho en SCADA: cuando un gateway empieza a informar TASK_WDT repetidamente, mantenimiento lo descubre antes de que el equipo se detenga.
Configura en SCADA una alarma por falta de actualización: si el t del estado es más viejo que dos minutos, el gateway desapareció. Una alarma por "tópico ausente" no sirve aquí, porque con retención el broker devuelve el último estado aunque el gateway esté muerto.
Retención: lo que ve SCADA al reiniciar
Un mensaje MQTT normal se entrega y se olvida. Si SCADA se reinicia, no tiene ningún valor hasta la siguiente publicación; en un sensor de temperatura ambiente que publica cada cinco minutos, son cinco minutos de pantalla vacía.
La flag de retención hace que el broker guarde el último mensaje de cada tópico y lo entregue a quien se suscriba. SCADA arranca y ya tiene el último valor conocido.
La regla práctica es: retén estados, no eventos.
Retén el valor del proceso porque el último valor conocido tiene significado y el estado del gateway, que por definición es un estado.
No retengas una alarma momentánea porque repetir una alarma antigua confunde al operador, ni un comando, porque repetirlo ejecuta otra vez la acción.
Este último caso es realmente peligroso. Un comando retenido puede volver a activar un equipo cuando SCADA se reconecta.
TLS en la planta: qué cambia
La comunicación con SCADA casi siempre exige TLS y en el ESP32 esto tiene tres consecuencias prácticas.
Cuesta RAM. El handshake consume decenas de KB. En un firmware ajustado el síntoma es un reinicio por falta de memoria, no un error de red; se pierden horas buscando un problema de conectividad.
Exige reloj. Validar el certificado depende de la fecha: un certificado válido parece caducado si la placa cree que está en 1970. Sincroniza NTP antes de intentar TLS.
Exige el certificado raíz. Va incluido en el firmware y caduca. Un día la conexión deja de funcionar sola, sin que nadie cambie nada. Anota la fecha de caducidad junto a la documentación del equipo; es el tipo de dato que nadie recuerda tres años después.
Sparkplug B: cuándo merece la pena
Si tu SCADA lo soporta, conviene conocerlo. Sparkplug B estandariza precisamente lo descrito en este artículo: jerarquía de tópicos, tipos de datos, calidad y nacimiento y muerte de dispositivos.
La ventaja real es el birth certificate: al conectarse, el dispositivo publica la lista completa de tags con tipo y unidad. SCADA descubre por sí solo lo que ofrece el gateway, sin registrar tags a mano.
El coste es la carga binaria, que exige una biblioteca en ambos lados y es mucho más pesada en el ESP32 que un JSON corto.
La recomendación honesta es: si tu SCADA ya habla Sparkplug y tienes decenas de gateways, compensa. Para pocos dispositivos con JSON bien modelado, la complejidad no compensa.
Tutorial: de publish a un tag en pantalla
Paso 1: acordar el modelo antes de programar
Siéntate con quien opera el SCADA antes de escribir la primera línea. Define la jerarquía de tópicos, los nombres de las magnitudes y las unidades.
Cambiarlo después significa reconfigurar SCADA y reprogramar todos los gateways. Es el paso más barato de hacer y el más caro de saltarse.
Paso 2: sincronizar el reloj antes que nada
Configura NTP y espera la sincronización antes de publicar o abrir TLS. Mientras no sincronice, publica con calidad incierta y sin el campo t: nunca inventes una hora.
Paso 3: publicar una sola magnitud
Empieza con un tópico. Confirma en SCADA que el tag aparece con valor, unidad y hora correctos.
Una magnitud funcionando de extremo a extremo vale más que veinte publicando mal.
Paso 4: añadir calidad y probar la degradación
Desconecta el sensor a propósito y confirma que el valor llega con mala calidad y que SCADA lo muestra en vez de presentar cero como si fuera una lectura.
Si SCADA no lo distingue, el problema está en la configuración del tag de ese lado.
Paso 5: heartbeat y alarma por ausencia
Publica el estado periódico y configura en SCADA la alarma por falta de actualización del tópico. Después apaga el gateway y cronometra hasta que salte la alarma.
Esta es la prueba que demuestra que se percibirá un fallo silencioso.
Paso 6: activar TLS
Solo después de que todo funcione en texto claro. Incluye el certificado raíz, confirma que el reloj está sincronizado y observa el heap libre después de conectarte.
Si la placa se reinicia aquí, es memoria, no red.
Paso 7: probar la reconexión con datos acumulados
Detén el broker unos minutos mientras el gateway sigue leyendo. Al volver, confirma que los datos llegan con la hora de lectura y no con la de reconexión.
Es la prueba que valida la marca de tiempo y casi nadie la hace.
Errores comunes y cómo resolverlos
SCADA no reconoce el dato. Modelado. El JSON debe acordarse antes; los nombres inventados en el firmware no se convierten en tags solos.
Todos los valores tienen la misma hora. La marca se está poniendo al llegar. Publica el instante de lectura en la carga.
El operador vio cero y lo creyó. Falta el campo de calidad o SCADA no está configurado para mostrarlo.
Alarma falsa cuando el valor no cambia. Falta heartbeat. Configura una alarma por falta de actualización del tópico de estado, no por un valor detenido.
SCADA se reinicia y no hay datos. Faltan retenciones en los tópicos de estado.
Un comando se ejecutó solo otra vez. Se publicó retenido. Nunca retengas comandos.
La conexión TLS dejó de funcionar sin que nadie cambiara nada. Certificado raíz caducado o reloj sin sincronizar.
Se reinicia al activar TLS. RAM del handshake. Usa una placa con PSRAM o reduce el firmware.
