IoT industrial

EtherNet/IP a MQTT: lectura de dispositivos CIP y tags Logix con Python

Usa librerías CIP de código abierto en un gateway de borde para leer tags de ControlLogix y CompactLogix y atributos de dispositivos EtherNet/IP genéricos, y publícalos como mensajes MQTT limpios.

Gateway de borde leyendo tags CIP de un rack PLC Logix y enviando mensajes de EtherNet/IP a MQTT a un broker en la nube
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

Un puente de EtherNet/IP a MQTT te permite sacar datos de controladores Rockwell Logix, variadores y otros dispositivos CIP y llevarlos a dashboards, historiadores y aplicaciones en la nube sin tocar el programa de control. En este tutorial aprenderás la diferencia entre mensajería implícita y explícita, y después usarás la librería de código abierto pycomm3 en un gateway para leer tags Logix y atributos CIP genéricos y publicarlos por MQTT con payloads limpios y autodescriptivos.

EtherNet/IP y CIP en cinco minutos

EtherNet/IP es el Common Industrial Protocol (CIP) transportado sobre Ethernet estándar y TCP/UDP. CIP modela cada dispositivo como un conjunto de objetos con clases, instancias y atributos. Un variador, por ejemplo, tiene un objeto Identity (clase 0x01) que describe fabricante, código de producto y número de serie, además de objetos de aplicación para velocidad, corriente y estado.

El tráfico CIP se presenta en dos variantes:

Tipo de mensajería Transporte Patrón Uso típico ¿Adecuado para un gateway?
Implícita (I/O) UDP, puerto 2222 Conexiones cíclicas y planificadas entre scanner y adapter Control en tiempo real de E/S y variadores Rara vez; exige ser el scanner o un adapter configurado
Explícita TCP, puerto 44818 Petición/respuesta Lectura de tags, atributos y diagnósticos Sí, es el enfoque habitual

Un gateway de monitorización debe usar mensajería explícita. No interfiere con las conexiones de E/S del controlador, no requiere cambios en el proyecto del PLC y se puede añadir o retirar sin paradas. El precio es que se trata de un protocolo de sondeo (polling), así que tú decides con qué frecuencia leer.

Dos tipos de destino

  1. Controladores Logix (ControlLogix, CompactLogix y, en parte, Micro800). Admiten acceso simbólico a tags: lees Line1_Speed o Conveyor[3].Status por su nombre. Es, con diferencia, la fuente de datos más cómoda.
  2. Dispositivos CIP genéricos (variadores, adaptadores de E/S, scanners, dispositivos de terceros). Exponen atributos de objetos que se leen por número de clase, instancia y atributo, según la documentación del dispositivo o su archivo EDS.

pycomm3 cubre ambos casos: LogixDriver para controladores basados en tags y CIPDriver para mensajes explícitos genéricos. pylogix es otra opción de código abierto muy popular para tags Logix. Siemens, Beckhoff y otras plataformas tienen sus propias vías de acceso; para temas específicos de Rockwell, como la configuración del controlador, consulta nuestra guía Allen-Bradley MQTT.

Requisitos previos

  • Python 3.10 o posterior en un gateway que alcance el controlador por TCP 44818.
  • pycomm3 y paho-mqtt 2.x.
  • La dirección IP del controlador y, en controladores de chasis, el número de slot (por ejemplo 10.0.0.20/0 para el slot 0).
  • Una lista de tags de solo lectura acordada con el ingeniero de control.
python -m venv .venv && source .venv/bin/activate
pip install pycomm3 "paho-mqtt>=2.0"

Paso 1: explorar el controlador

Antes de escribir el puente, confirma la conectividad y lista los tags disponibles. Este script muestra la información del controlador y los primeros tags de ámbito de controlador.

# explore_logix.py - confirm connectivity and list tags
from pycomm3 import LogixDriver

with LogixDriver("10.0.0.20/0") as plc:          # IP/slot of the controller
    print(plc.info)                               # vendor, product, revision, name
    tags = plc.get_tag_list()                     # controller-scoped tags
    for t in tags[:30]:
        print(t["tag_name"], t["data_type_name"])

get_tag_list() descarga las definiciones de tags, incluidos los tipos definidos por el usuario, por lo que puede tardar un poco en proyectos grandes. Los tags de ámbito de programa se leen con la sintaxis Program:MainProgram.TagName.

Paso 2: leer un atributo CIP genérico

Para un variador o cualquier dispositivo CIP sin tags simbólicos, usa mensajes explícitos. El ejemplo lee el nombre del producto del objeto Identity, que implementan todos los dispositivos EtherNet/IP.

# identity.py - read Identity object attribute 7 (Product Name) from any CIP device
from pycomm3 import CIPDriver, Services, ClassCode, SHORT_STRING

with CIPDriver("10.0.0.31") as dev:
    resp = dev.generic_message(
        service=Services.get_attribute_single,
        class_code=ClassCode.identity_object,   # 0x01
        instance=1,
        attribute=7,                             # Product Name
        data_type=SHORT_STRING,
        connected=False,
        name="product_name",
    )
    print(resp.value if not resp.error else resp.error)

Sustituye clase, instancia, atributo y tipo de dato por los valores del manual del dispositivo para leer datos de aplicación como la frecuencia de salida o la corriente del motor.

Paso 3: el puente de EtherNet/IP a MQTT

En cada ciclo, el puente lee una lista de tags con una sola petición multitag, publica los valores que han cambiado (o que superan una banda muerta) e informa de los errores de lectura como calidad mala en lugar de conservar en silencio los valores antiguos.

# eip_mqtt_bridge.py - poll Logix tags over EtherNet/IP, publish to MQTT
import json, logging, time, uuid
import paho.mqtt.client as mqtt
from pycomm3 import LogixDriver, CommError

logging.basicConfig(level=logging.INFO)
log = logging.getLogger("eip")

PLC_PATH = "10.0.0.20/0"
POLL_SECONDS = 1.0
TOPIC_ROOT = "connectmq/demo/9e6d/plant1/packaging/line2"   # your own random id
MQTT_HOST, MQTT_PORT = "broker.connectmq.com", 1883         # production: 8883 with TLS
MQTT_USER, MQTT_PASS = None, None                           # production: "YOUR_USERNAME", "YOUR_PASSWORD"
CLIENT_ID = f"eip-bridge-{uuid.uuid4().hex[:8]}"            # production: "YOUR_CLIENT_ID"

# PLC tag -> (topic leaf, unit, deadband)
TAGS = {
    "Line2_Speed":          ("speed", "ppm", 1.0),
    "Line2_Running":        ("running", "", 0),
    "Line2_GoodCount":      ("good_count", "count", 0),
    "Line2_RejectCount":    ("reject_count", "count", 0),
    "Filler.MotorCurrent":  ("filler_current", "A", 0.2),
}


def mqtt_client():
    c = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id=CLIENT_ID)
    if MQTT_USER:
        c.username_pw_set(MQTT_USER, MQTT_PASS)
        c.tls_set()
    c.will_set(f"{TOPIC_ROOT}/bridge", "offline", qos=1, retain=True)
    c.on_connect = lambda cl, u, f, rc, p: cl.publish(f"{TOPIC_ROOT}/bridge", "online", qos=1, retain=True)
    c.connect(MQTT_HOST, MQTT_PORT, keepalive=30)
    c.loop_start()
    return c


def changed(old, new, deadband):
    if old is None:
        return True
    if isinstance(new, (int, float)) and not isinstance(new, bool) and deadband:
        return abs(new - old) >= deadband
    return new != old


def main():
    mq = mqtt_client()
    last = {}
    while True:
        try:
            with LogixDriver(PLC_PATH) as plc:
                log.info("connected to %s", plc.info.get("product_name"))
                while True:
                    results = plc.read(*TAGS.keys())
                    if not isinstance(results, list):
                        results = [results]
                    now = time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
                    for r in results:
                        leaf, unit, deadband = TAGS[r.tag]
                        topic = f"{TOPIC_ROOT}/{leaf}"
                        if r.error:
                            if last.get(r.tag, {}).get("quality") != "bad":
                                mq.publish(topic, json.dumps({"value": None, "quality": "bad",
                                                              "error": r.error, "ts": now}), qos=1, retain=True)
                                last[r.tag] = {"value": None, "quality": "bad"}
                            continue
                        prev = last.get(r.tag, {})
                        if prev.get("quality") == "bad" or changed(prev.get("value"), r.value, deadband):
                            payload = {"value": r.value, "unit": unit, "type": r.type,
                                       "quality": "good", "ts": now}
                            mq.publish(topic, json.dumps(payload), qos=1, retain=True)
                            last[r.tag] = {"value": r.value, "quality": "good"}
                    time.sleep(POLL_SECONDS)
        except CommError as exc:
            log.error("EtherNet/IP connection lost: %s - retrying in 5 s", exc)
            time.sleep(5)


if __name__ == "__main__":
    main()

plc.read() con varios nombres de tag los agrupa en tan pocas peticiones como permita el tamaño de la conexión, lo que supone mucha menos carga para el controlador que una petición por tag. Cada resultado es un Tag con los campos tag, value, type y error, de modo que el puente puede publicar el tipo de dato junto al valor.

Paso 4: ejecutar y verificar

python eip_mqtt_bridge.py
mosquitto_sub -h broker.connectmq.com -p 1883 -i eip-viewer-9e6d -t "connectmq/demo/9e6d/#" -v
connectmq/demo/9e6d/plant1/packaging/line2/speed {"value": 118.0, "unit": "ppm", "type": "REAL", "quality": "good", "ts": "2026-10-16T12:00:01Z"}
connectmq/demo/9e6d/plant1/packaging/line2/running {"value": true, "unit": "", "type": "BOOL", "quality": "good", "ts": "2026-10-16T12:00:01Z"}

El árbol de topics sigue una estructura de estilo ISA-95 (planta, área, línea, punto), así que los mismos consumidores funcionan tanto si los datos vienen de EtherNet/IP como de Modbus u OPC UA. Nuestra guía de topics MQTT ISA-95 explica las convenciones.

Elegir tags y frecuencias de sondeo

No todos los tags de un proyecto Logix merecen un topic. Parte de las preguntas que harán los consumidores: ¿está la línea en marcha?, ¿a qué velocidad?, ¿cuántas unidades buenas y rechazadas?, ¿qué alarmas están activas? y ¿cuánta energía consumen los variadores principales? Normalmente eso supone unas pocas decenas de valores por línea, no miles. Agrúpalos según lo rápido que cambian. Estados y contadores pueden leerse cada segundo y publicarse al cambiar; los valores analógicos lentos, como niveles de depósito o temperaturas, pueden leerse cada cinco o diez segundos con banda muerta. Si un valor solo es útil como total de turno, plantéate dejar que el PLC lo acumule y publicarlo en un ciclo más lento.

Buenas prácticas para producción

  1. Solo lectura por diseño. No añadas escritura a un puente de monitorización. Si alguna vez necesitas enviar comandos, crea un servicio aparte con lógica de aprobación explícita.
  2. Usa un conjunto de tags de interfaz. Pide al ingeniero de control que copie los valores que necesitas en un UDT o array dedicado. Así lees una sola estructura en lugar de decenas de tags dispersos, y los cambios de programa no rompen el puente.
  3. Vigila los límites de conexiones. Los controladores admiten un número limitado de conexiones CIP, compartidas con HMI, SCADA y herramientas de ingeniería. Lo ideal es una conexión de larga duración por puente; evita reconectar en cada ciclo.
  4. Sondea a la velocidad que necesitas. Un segundo es más que suficiente para contadores y estados de producción. Sondear más rápido añade carga sin aportar información.
  5. Segmentación de red. Coloca el gateway en la red OT o en una DMZ con acceso solo al puerto 44818 de los controladores y salida por el 8883 hacia el broker. Nuestro artículo sobre MQTT en una DMZ describe la arquitectura.
  6. Broker privado para datos reales. El servidor MQTT público gratuito sirve para pruebas y prototipos, pero lo puede leer cualquiera. Pasa a un broker MQTT gestionado con TLS y credenciales por broker antes de conectar una línea real.

Solución de problemas

Síntoma Causa probable Solución
CommError al conectar IP incorrecta, firewall bloqueando el 44818, slot incorrecto Haz ping al controlador y revisa la ruta ip/slot
La lectura de tags devuelve Tag doesn't exist Tag de ámbito de programa o errata Usa Program:Name.Tag y revisa get_tag_list()
Funciona y falla al cabo de horas El controlador o el switch cortó la conexión El bucle de reintento reconecta; revisa el uso de conexiones del controlador
El mensaje genérico devuelve un error de estado CIP Clase, instancia, atributo o tipo de dato incorrectos Consulta el manual del dispositivo o el archivo EDS
Los valores se actualizan despacio Lista de tags grande dividida en muchas peticiones Usa un UDT de interfaz y reduce el número de tags

Dónde encaja ConnectMQ

ConnectMQ es el lado cloud de esta cadena: un broker MQTT gestionado con TLS en el puerto 8883 y WebSockets para dashboards en el navegador. Detecta estructuras de topics ISA-95, de modo que la vista de dispositivos agrupa líneas y máquinas automáticamente, y las reglas de alerta por umbral pueden avisar cuando un contador de rechazos o la corriente de un motor se salen de sus límites. Con los tags ya organizados por línea y máquina, el mismo flujo puede alimentar un gemelo digital 3D de la planta, por ejemplo una línea de envasado en Querétaro o en Barcelona. Si eres nuevo en los brokers MQTT gestionados, mira cómo se compara un broker MQTT en la nube con el autoalojamiento y, para la arquitectura completa, lee la guía de MQTT para IoT industrial.

Empieza con ConnectMQ

Conecta hoy tu primera línea EtherNet/IP. Regístrate en app.connectmq.com, elige el plan Serverless gratuito sin tarjeta de crédito, crea un broker y pega en el puente el usuario, la contraseña y el client ID generados. Tus tags Logix aparecerán en el explorador de topics de ConnectMQ en cuestión de segundos.

Preguntas frecuentes

¿Leer tags por EtherNet/IP afecta al programa del PLC?

La mensajería explícita usa los recursos de comunicación del controlador, no sus conexiones de E/S, y no modifica el programa. Mantén un sondeo moderado y pocas conexiones para no afectar a HMI y SCADA.

¿Puedo usar mensajería implícita de E/S para un gateway MQTT?

Es posible si el gateway se configura como adapter en el árbol de E/S del PLC, pero eso requiere un cambio en el proyecto. Para monitorización, la lectura explícita de tags es más sencilla y no intrusiva.

¿Funciona pycomm3 con Micro800 y con controladores PLC-5 o SLC más antiguos?

pycomm3 está orientado a controladores Logix y tiene soporte parcial para Micro800; las familias PLC-5 y SLC más antiguas usan otro direccionamiento. Consulta la documentación de la librería para tu controlador concreto.

¿Qué puerto usa la mensajería explícita de EtherNet/IP?

El puerto TCP 44818. El tráfico de E/S implícito usa el puerto UDP 2222.

Artículos relacionados

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.