Saltar al contenido
Todo Automatizado
Automatización Industrial

Python Modbus TCP: Tutorial Completo para Comunicarte con un PLC

Aprende a usar Python con Modbus TCP para leer y escribir datos de un PLC. Tutorial con PyModbus, ejemplos reales y código listo para producción.

Carlos 15 min de lectura

Python Modbus TCP: Tutorial Completo para Comunicarte con un PLC

Si tienes un PLC en planta y quieres leer sus datos desde Python — sin depender de software propietario de miles de euros — estás en el sitio correcto. Este tutorial de Python Modbus TCP te enseña a establecer comunicación directa con un PLC usando PyModbus, la librería de referencia para el protocolo Modbus en Python.

Vamos al grano: instalar PyModbus, conectar con un PLC real (o simulado), leer registros, escribir valores y construir un script robusto que puedas usar en producción. Todo con código que funciona, explicado línea por línea.

Qué Es Modbus TCP y Por Qué Sigue Siendo Relevante

Modbus nació en 1979. Más de 45 años después, sigue siendo el protocolo más extendido en automatización industrial. ¿La razón? Es simple, abierto y funciona. La especificación Modbus es pública — cualquiera puede implementarla sin pagar licencias ni royalties.

Modbus TCP es la variante que viaja sobre Ethernet usando TCP/IP. A diferencia del Modbus RTU clásico (que va por RS-485 y cable serie), Modbus TCP te permite comunicar con PLCs, medidores de energía, inversores de frecuencia y sensores a través de la red estándar de tu planta.

El modelo de datos de Modbus es directo. Solo necesitas entender cuatro tipos de registros:

  • Coils (0xxxx): Bits de lectura/escritura. Salidas digitales, activación de motores, válvulas.
  • Discrete Inputs (1xxxx): Bits de solo lectura. Sensores digitales, finales de carrera, estados.
  • Input Registers (3xxxx): Registros de 16 bits de solo lectura. Mediciones analógicas: temperatura, presión, caudal.
  • Holding Registers (4xxxx): Registros de 16 bits de lectura/escritura. Setpoints, parámetros de configuración, consignas.

Cada PLC expone un mapa de registros Modbus documentado por el fabricante. Tu trabajo como programador es saber qué registro corresponde a qué dato — y ahí es donde Python entra en juego.

Por Qué Python para Comunicación con PLCs

Python no es la primera opción que viene a la mente cuando piensas en comunicaciones industriales. Pero tiene ventajas concretas frente a herramientas tradicionales como SCADA o software del fabricante:

Flexibilidad total. Lees datos del PLC y los procesas como quieras: los metes en una base de datos, los envías a un dashboard, aplicas un algoritmo de detección de anomalías o lanzas una alerta por Telegram. Todo en el mismo script.

Coste cero en licencias. PyModbus es open source. Compáralo con los módulos de comunicación de Ignition, WinCC o FactoryTalk — que pueden costar entre 2.000 € y 15.000 € por licencia.

Integración con el ecosistema de datos. Pandas, NumPy, scikit-learn, SQLAlchemy, Flask, Grafana… todo el stack de data science y desarrollo web habla Python. Conectar un PLC al mundo de los datos nunca fue tan directo.

Automatización y scripting. Puedes programar lecturas periódicas, cron jobs, pipelines de datos o servicios systemd que corren 24/7 sin intervención manual.

¿La pega? Python no es tiempo real. No lo uses para lazos de control críticos donde un milisegundo importa — para eso está el propio PLC. Pero para supervisión, adquisición de datos, logging y análisis, Python es la herramienta perfecta.

Instalar PyModbus y Preparar el Entorno

PyModbus es la librería estándar para Modbus en Python. Lleva años en desarrollo activo, tiene cobertura de tests del 100 % y soporta Modbus TCP, RTU, TLS y UDP. La versión actual (3.x) utiliza API asíncrona por defecto, aunque ofrece también cliente síncrono.

Preparemos el entorno:

# Crear directorio del proyecto
mkdir modbus-python && cd modbus-python

# Entorno virtual (no negociable)
python3 -m venv .venv
source .venv/bin/activate

# Instalar PyModbus
pip install pymodbus

Requisitos mínimos: Python 3.10 o superior. PyModbus no tiene dependencias externas obligatorias — solo necesita pyserial si vas a usar comunicación serie (RS-485).

Simular un PLC para pruebas

Si no tienes un PLC físico a mano, PyModbus incluye un servidor que actúa como simulador. Perfecto para desarrollar y depurar antes de conectarte a la máquina real.

Crea un archivo simulador_plc.py:

"""Simulador de PLC Modbus TCP para desarrollo y pruebas."""

from pymodbus.server import StartTcpServer
from pymodbus.datastore import (
    ModbusSequentialDataBlock,
    ModbusSlaveContext,
    ModbusServerContext,
)

# Crear bloques de datos simulados
# Cada bloque: dirección inicial 0, con 100 registros
store = ModbusSlaveContext(
    di=ModbusSequentialDataBlock(0, [0] * 100),   # Discrete Inputs
    co=ModbusSequentialDataBlock(0, [0] * 100),   # Coils
    hr=ModbusSequentialDataBlock(0, [0] * 100),   # Holding Registers
    ir=ModbusSequentialDataBlock(0, [0] * 100),   # Input Registers
)

# Pre-cargar datos de ejemplo
# Simular temperatura (registro 0), presión (registro 1), caudal (registro 2)
store.store["ir"].setValues(0, [2350, 1013, 450])  # 23.5°C, 10.13 bar, 4.50 m³/h
store.store["hr"].setValues(0, [2500, 1100, 500])   # Setpoints correspondientes

context = ModbusServerContext(slaves=store, single=True)

print("Simulador PLC arrancado en 0.0.0.0:502")
print("Input Registers: temp=2350, presion=1013, caudal=450")
print("Holding Registers: sp_temp=2500, sp_presion=1100, sp_caudal=500")

StartTcpServer(context=context, address=("0.0.0.0", 502))

Arráncalo en una terminal:

# Puerto 502 requiere privilegios (estándar Modbus)
sudo python simulador_plc.py

# Alternativa: usar un puerto alto para evitar sudo
# Cambia address=("0.0.0.0", 5020) en el script

Déjalo corriendo — ahora vamos a conectarnos desde el cliente.

Tu Primer Cliente Modbus TCP en Python

Aquí es donde empieza lo divertido. Crea un archivo leer_plc.py:

"""Cliente Modbus TCP básico: leer datos de un PLC."""

from pymodbus.client import ModbusTcpClient

# Conexión al PLC (o al simulador)
PLC_IP = "192.168.1.10"  # Cambia por la IP de tu PLC
PLC_PORT = 502            # Puerto estándar Modbus TCP

client = ModbusTcpClient(PLC_IP, port=PLC_PORT, timeout=3)
client.connect()

# Leer 3 Input Registers desde la dirección 0
result = client.read_input_registers(address=0, count=3, slave=1)

if not result.isError():
    temperatura = result.registers[0] / 100  # Escalado: 2350 → 23.50 °C
    presion = result.registers[1] / 100       # 1013 → 10.13 bar
    caudal = result.registers[2] / 100        # 450 → 4.50 m³/h

    print(f"Temperatura: {temperatura} °C")
    print(f"Presión:     {presion} bar")
    print(f"Caudal:      {caudal} m³/h")
else:
    print(f"Error leyendo registros: {result}")

client.close()

Ejecútalo:

python leer_plc.py
# Temperatura: 23.5 °C
# Presión:     10.13 bar
# Caudal:      4.5 m³/h

Seis líneas operativas. Eso es todo lo que necesitas para establecer comunicación Python Modbus TCP con un PLC. El resto es escalar a partir de aquí.

Puntos clave del código

  • slave=1: Identificador del dispositivo Modbus (Unit ID). Si tu PLC solo tiene un esclavo, normalmente es 1. Revisa la documentación de tu equipo.
  • timeout=3: Tres segundos de espera máxima. En redes industriales, 3 s es generoso — si no responde en ese tiempo, algo va mal.
  • Escalado de valores: Los registros Modbus son enteros de 16 bits (0–65535). Los fabricantes codifican valores decimales multiplicando por 10, 100 o 1000. Siempre revisa la tabla de registros del PLC para conocer el factor de escala.

Escribir Valores en el PLC: Cambiar Setpoints

Leer está bien, pero a veces necesitas escribir: cambiar un setpoint, activar una salida o enviar un comando. Cuidado aquí — estás modificando el comportamiento de una máquina real. Asegúrate de tener permiso del responsable de planta antes de escribir en un PLC en producción.

"""Escribir un holding register: cambiar setpoint de temperatura."""

from pymodbus.client import ModbusTcpClient

client = ModbusTcpClient("192.168.1.10", port=502, timeout=3)
client.connect()

# Nuevo setpoint de temperatura: 25.00 °C → registro = 2500
nuevo_setpoint = 2500
result = client.write_register(address=0, value=nuevo_setpoint, slave=1)

if not result.isError():
    print(f"Setpoint actualizado a {nuevo_setpoint / 100} °C")
else:
    print(f"Error escribiendo: {result}")

# Verificar la escritura leyendo de vuelta
check = client.read_holding_registers(address=0, count=1, slave=1)
if not check.isError():
    print(f"Verificación: registro 0 = {check.registers[0]}")

client.close()

Para escribir múltiples registros de golpe:

# Escribir 3 setpoints de una vez (más eficiente que 3 escrituras individuales)
setpoints = [2500, 1100, 500]  # temp, presion, caudal
result = client.write_registers(address=0, values=setpoints, slave=1)

Y para trabajar con coils (salidas digitales):

# Activar la salida digital 5 (por ejemplo, arrancar un motor)
client.write_coil(address=5, value=True, slave=1)

# Desactivar la salida digital 5
client.write_coil(address=5, value=False, slave=1)

# Leer el estado de 8 coils desde la dirección 0
coils = client.read_coils(address=0, count=8, slave=1)
print(coils.bits[:8])  # [False, False, False, False, False, True, False, False]

Lectura Continua: Monitorización en Tiempo Real

Un script que lee una vez y se cierra tiene utilidad limitada. Lo que necesitas en planta es monitorización continua: leer datos cada X segundos y hacer algo con ellos (guardarlos, analizarlos, alertar si se salen de rango).

"""Monitor Modbus TCP: lectura continua con logging y alertas."""

import time
import logging
from datetime import datetime
from pymodbus.client import ModbusTcpClient

# Configurar logging
logging.basicConfig(
    filename="monitor_plc.log",
    level=logging.INFO,
    format="%(asctime)s | %(levelname)s | %(message)s",
)
logger = logging.getLogger(__name__)

# Configuración
PLC_IP = "192.168.1.10"
INTERVALO_LECTURA = 5  # segundos
LIMITE_TEMP = 30.0      # Alarma si supera 30 °C

client = ModbusTcpClient(PLC_IP, port=502, timeout=3)


def leer_y_procesar():
    """Lee registros del PLC, procesa datos y genera alertas."""
    if not client.connected:
        client.connect()

    result = client.read_input_registers(address=0, count=3, slave=1)

    if result.isError():
        logger.error(f"Error de lectura: {result}")
        return

    temperatura = result.registers[0] / 100
    presion = result.registers[1] / 100
    caudal = result.registers[2] / 100

    # Logging normal
    logger.info(f"T={temperatura}°C | P={presion}bar | Q={caudal}m³/h")

    # Alarma por temperatura alta
    if temperatura > LIMITE_TEMP:
        logger.warning(
            f"ALARMA: Temperatura {temperatura}°C supera límite de {LIMITE_TEMP}°C"
        )
        # Aquí podrías enviar alerta por email, Telegram, etc.


def main():
    """Bucle principal de monitorización."""
    logger.info("Monitor arrancado")
    print(f"Monitorizando PLC en {PLC_IP} cada {INTERVALO_LECTURA}s...")
    print("Ctrl+C para detener")

    try:
        while True:
            leer_y_procesar()
            time.sleep(INTERVALO_LECTURA)
    except KeyboardInterrupt:
        print("\nMonitor detenido por el usuario")
        logger.info("Monitor detenido")
    finally:
        client.close()


if __name__ == "__main__":
    main()

Este script es la base sobre la que puedes construir casi cualquier cosa: insertar datos en PostgreSQL, alimentar un dashboard con Flask (mira nuestro tutorial de dashboards industriales con Python y Flask), enviar alertas cuando un valor se sale de rango o integrar con n8n para automatizar workflows completos.

Valores de 32 Bits y Números con Decimales (Float)

Los registros Modbus son de 16 bits. ¿Qué pasa cuando necesitas un valor mayor que 65.535 o un número en coma flotante IEEE 754? Usas dos registros consecutivos.

PyModbus incluye utilidades para convertir entre registros y tipos de datos Python:

"""Leer un valor float IEEE 754 de 32 bits (2 registros Modbus)."""

from pymodbus.client import ModbusTcpClient
from pymodbus.payload import BinaryPayloadDecoder
from pymodbus.constants import Endian

client = ModbusTcpClient("192.168.1.10", port=502)
client.connect()

# Leer 2 registros consecutivos (32 bits en total)
result = client.read_holding_registers(address=10, count=2, slave=1)

if not result.isError():
    # Decodificar como float IEEE 754
    # IMPORTANTE: el orden de bytes depende del fabricante del PLC
    decoder = BinaryPayloadDecoder.fromRegisters(
        result.registers,
        byteorder=Endian.BIG,
        wordorder=Endian.BIG,
    )
    valor_float = decoder.decode_32bit_float()
    print(f"Valor float: {valor_float:.2f}")

client.close()

Atención al byte order. Los PLCs de Siemens suelen usar Big Endian (AB CD). Los de Schneider y Allen-Bradley pueden usar distintas combinaciones: Big Endian, Little Endian (CD AB) o incluso word-swapped. Si lees un valor float y te sale algo absurdo como 2.37e+18, prueba a cambiar byteorder y wordorder. Es el error más común al trabajar con Modbus y floats.

Fabricante típicobyteorderwordorder
Siemens S7BIGBIG
Schneider M340BIGLITTLE
Allen-BradleyLITTLEBIG
WagoBIGBIG

Consulta siempre la documentación de tu PLC. Y si no la tienes, prueba las cuatro combinaciones — solo hay cuatro posibilidades.

Cliente Asíncrono para Alto Rendimiento

Si necesitas leer múltiples PLCs o cientos de registros con alta frecuencia, el cliente síncrono se queda corto. PyModbus ofrece un cliente asíncrono basado en asyncio que es hasta 300 veces más rápido según los benchmarks de la propia librería.

"""Cliente Modbus TCP asíncrono: leer múltiples PLCs en paralelo."""

import asyncio
from pymodbus.client import AsyncModbusTcpClient


async def leer_plc(ip: str, nombre: str):
    """Lee datos de un PLC específico."""
    client = AsyncModbusTcpClient(ip, port=502, timeout=3)
    await client.connect()

    result = await client.read_input_registers(address=0, count=3, slave=1)

    if not result.isError():
        temp = result.registers[0] / 100
        print(f"[{nombre}] Temperatura: {temp}°C")
    else:
        print(f"[{nombre}] Error: {result}")

    client.close()


async def main():
    """Leer 3 PLCs en paralelo."""
    await asyncio.gather(
        leer_plc("192.168.1.10", "Línea 1"),
        leer_plc("192.168.1.11", "Línea 2"),
        leer_plc("192.168.1.12", "Línea 3"),
    )


asyncio.run(main())

Con asyncio.gather lanzas las tres lecturas simultáneamente. Mientras esperas la respuesta de un PLC, estás procesando la de otro. En una planta con 10 o 20 dispositivos Modbus, la diferencia de rendimiento es enorme.

Gestión de Errores y Reconexión Automática

En producción, la conexión se va a caer. Un cable suelto, un switch que reinicia, un PLC que se apaga por mantenimiento. Tu script tiene que sobrevivir a todo eso sin quedarse muerto.

"""Cliente Modbus TCP robusto con reconexión automática."""

import time
import logging
from pymodbus.client import ModbusTcpClient
from pymodbus.exceptions import ConnectionException, ModbusIOException

logger = logging.getLogger(__name__)

MAX_REINTENTOS = 5
ESPERA_RECONEXION = 10  # segundos


def crear_cliente(ip: str, port: int = 502) -> ModbusTcpClient:
    """Crea un cliente con configuración robusta."""
    return ModbusTcpClient(
        ip,
        port=port,
        timeout=3,
        retries=3,            # Reintentos automáticos por petición
        reconnect_delay=5,    # Espera entre reconexiones (segundos)
    )


def leer_con_reintento(client, address, count, slave=1):
    """Lee registros con gestión de errores y reconexión."""
    for intento in range(1, MAX_REINTENTOS + 1):
        try:
            if not client.connected:
                logger.info("Reconectando...")
                client.connect()

            result = client.read_input_registers(
                address=address, count=count, slave=slave
            )

            if result.isError():
                logger.warning(f"Intento {intento}: respuesta con error: {result}")
                time.sleep(2)
                continue

            return result.registers

        except ConnectionException:
            logger.error(f"Intento {intento}: sin conexión con el PLC")
            time.sleep(ESPERA_RECONEXION)
        except ModbusIOException as e:
            logger.error(f"Intento {intento}: error I/O: {e}")
            time.sleep(ESPERA_RECONEXION)

    logger.critical(f"Fallo tras {MAX_REINTENTOS} intentos. Abortando lectura.")
    return None

Este patrón de reintentos con backoff es la diferencia entre un script de laboratorio y uno que aguanta meses en producción sin caerse. Combínalo con systemd para que el servicio se reinicie automáticamente si el proceso muere:

# /etc/systemd/system/monitor-plc.service
[Unit]
Description=Monitor Modbus TCP
After=network.target

[Service]
Type=simple
User=operador
WorkingDirectory=/opt/monitor-plc
ExecStart=/opt/monitor-plc/.venv/bin/python monitor.py
Restart=always
RestartSec=30

[Install]
WantedBy=multi-user.target

Seguridad: Lo Que Nadie Te Cuenta

Modbus TCP no tiene autenticación ni cifrado. Cero. Cualquiera con acceso a la red puede leer y escribir registros del PLC. Esto no es un bug — Modbus se diseñó en 1979 para redes aisladas.

Si usas Python Modbus TCP en un entorno real, toma estas precauciones:

  • Segmentación de red. El PLC debe estar en una VLAN industrial separada. Tu servidor Python accede a esa VLAN a través de un firewall que solo permite tráfico Modbus (puerto 502) desde IPs autorizadas.
  • No expongas el PLC a internet. Nunca. Ni con VPN mal configurada. Herramientas como Shodan encuentran miles de dispositivos Modbus expuestos — no seas uno de ellos.
  • Acceso de solo lectura cuando sea posible. Si tu script solo necesita monitorizar, no le des permiso de escritura. Algunos PLCs permiten configurar permisos por conexión.
  • Logging de todas las escrituras. Registra quién escribió qué, cuándo y desde dónde. Si algo va mal en planta, el log es tu mejor aliado para investigar.

Si necesitas profundizar en ciberseguridad industrial, revisa nuestra guía de IEC 62443 — el estándar internacional para proteger sistemas de control.

Ejemplo Completo: Datalogger Modbus a CSV

Combinemos todo en un ejemplo práctico que puedas adaptar directamente. Este script lee datos de un PLC cada 10 segundos y los guarda en un archivo CSV:

"""Datalogger Modbus TCP → CSV. Listo para producción."""

import csv
import time
import os
from datetime import datetime
from pymodbus.client import ModbusTcpClient
from pymodbus.exceptions import ConnectionException

# Configuración — adapta a tu PLC
PLC_IP = "192.168.1.10"
PLC_PORT = 502
SLAVE_ID = 1
INTERVALO = 10  # segundos
CSV_FILE = "datos_plc.csv"

# Mapa de registros — documenta qué es cada registro
REGISTROS = {
    "temperatura": {"address": 0, "type": "input", "scale": 100, "unit": "°C"},
    "presion":     {"address": 1, "type": "input", "scale": 100, "unit": "bar"},
    "caudal":      {"address": 2, "type": "input", "scale": 100, "unit": "m³/h"},
    "sp_temp":     {"address": 0, "type": "holding", "scale": 100, "unit": "°C"},
}


def inicializar_csv(filepath):
    """Crea el CSV con cabeceras si no existe."""
    if not os.path.exists(filepath):
        with open(filepath, "w", newline="") as f:
            writer = csv.writer(f)
            headers = ["timestamp"] + list(REGISTROS.keys())
            writer.writerow(headers)


def leer_registros(client):
    """Lee todos los registros definidos en REGISTROS."""
    datos = {}
    for nombre, config in REGISTROS.items():
        if config["type"] == "input":
            result = client.read_input_registers(
                address=config["address"], count=1, slave=SLAVE_ID
            )
        else:
            result = client.read_holding_registers(
                address=config["address"], count=1, slave=SLAVE_ID
            )

        if not result.isError():
            datos[nombre] = result.registers[0] / config["scale"]
        else:
            datos[nombre] = None

    return datos


def main():
    inicializar_csv(CSV_FILE)
    client = ModbusTcpClient(PLC_IP, port=PLC_PORT, timeout=3)

    print(f"Datalogger Modbus → {CSV_FILE}")
    print(f"PLC: {PLC_IP}:{PLC_PORT} | Intervalo: {INTERVALO}s")
    print("Ctrl+C para detener\n")

    try:
        while True:
            try:
                if not client.connected:
                    client.connect()

                datos = leer_registros(client)
                timestamp = datetime.now().isoformat()

                # Escribir en CSV
                with open(CSV_FILE, "a", newline="") as f:
                    writer = csv.writer(f)
                    row = [timestamp] + [datos[k] for k in REGISTROS]
                    writer.writerow(row)

                # Mostrar en consola
                valores = " | ".join(
                    f"{k}={v}{REGISTROS[k]['unit']}"
                    for k, v in datos.items()
                    if v is not None
                )
                print(f"[{timestamp[:19]}] {valores}")

            except ConnectionException:
                print(f"[{datetime.now():%H:%M:%S}] Sin conexión. Reintentando...")
                time.sleep(5)

            time.sleep(INTERVALO)

    except KeyboardInterrupt:
        print("\nDatalogger detenido")
    finally:
        client.close()


if __name__ == "__main__":
    main()

Resultado: un CSV limpio con marca de tiempo que luego puedes importar en Excel, Pandas, Grafana o lo que necesites.

Próximos Pasos: De Script a Sistema

Ya tienes las bases de la comunicación Python Modbus TCP con PLCs. Desde aquí puedes escalar en varias direcciones:

  • Guardar en base de datos en lugar de CSV. PostgreSQL con TimescaleDB es ideal para series temporales industriales.
  • Montar un dashboard web con Flask o Grafana. Tenemos un tutorial de dashboard industrial con Flask que parte exactamente de donde termina este artículo.
  • Conectar con n8n para automatizar workflows: alertas, informes, integración con ERPs. Mira nuestro tutorial de n8n con Modbus vía API REST.
  • Añadir OPC-UA si tu planta tiene PLCs modernos que lo soporten. Nuestro tutorial de OPC-UA con Python cubre ese camino.
  • Aplicar IA a los datos: detección de anomalías, mantenimiento predictivo, optimización de procesos.

La combinación de Python y Modbus TCP te abre la puerta a todo el ecosistema de datos moderno sin depender de software propietario ni licencias caras. Empieza con un PLC y un script — desde ahí, el límite lo pones tú.


¿Necesitas ayuda para integrar tus PLCs con sistemas IT? En automatizatodo.com diseñamos soluciones de integración IT/OT a medida: desde la lectura de datos de planta hasta dashboards en tiempo real y automatización con IA. Escríbenos y cuéntanos tu caso.

#Python #Modbus TCP #PLC #PyModbus #automatización industrial #comunicación industrial #OT #IT/OT #industria 4.0 #protocolo Modbus

Sigue leyendo