Guía completa: Modbus RTU y TCP con Python y Node-RED
Para escribir y leer un coil por Modbus TCP desde un Raspberry PLC en Node-RED, instala los nodos node-red-contrib-modbus y crea un Modbus-Client con Type TCP, la dirección IP del esclavo y el puerto 502. Después usa un nodo Modbus Write con FC 5 y un nodo Modbus Read con FC 1. Pon a los dos nodos el mismo Unit-Id, el que espera el equipo (a menudo 1 o 255). El flujo de este post se ha probado con Node-RED 4.1 y node-red-contrib-modbus 5 contra un esclavo simulado.
Esta es la parte 2 de la serie Modbus y Raspberry PLC. Las partes 1 y 2 usan Node-RED, y las partes 3 y 4 usan Python. Todos los detalles, como timeouts, direccionamiento, valores de 32 bits y diagnóstico de fallos, están en la guía de Modbus para PLC con Raspberry Pi.
Qué necesitas
- Un Raspberry PLC con Node-RED. Es el cliente (maestro) Modbus TCP. Si todavía no tienes Node-RED instalado, consulta cómo instalar Node-RED en un Raspberry PLC.
- Un servidor (esclavo) Modbus TCP: un contador, un variador, otro PLC o un gateway. Para probar sin equipo, ejecuta en el mismo PLC el servidor Modbus TCP en Python de la guía de Modbus y usa la dirección
127.0.0.1y el puerto5020. - Una conexión Ethernet entre el PLC y el esclavo, los dos en la misma subred. Por defecto,
eth0del Raspberry PLC tiene la dirección 10.10.10.20. Para cambiarla, consulta cómo configurar una IP fija en un Raspberry PLC. - La dirección IP, el puerto TCP y el Unit-Id del esclavo. Este post usa un esclavo en 10.10.10.4, puerto 502, Unit-Id 1.
Arranca Node-RED e instala los nodos Modbus
Arranca Node-RED en un terminal del PLC:
node-red-start
Abre el editor en un navegador. Desde otro ordenador, usa http://PLC_IP_ADDRESS:1880. En el propio PLC, con una pantalla conectada, usa http://127.0.0.1:1880.
Abre Menu, Manage palette (o pulsa Alt+Mayús+P), ve a la pestaña Install, busca node-red-contrib-modbus e instálalo. La versión 5.62 de estos nodos necesita Node-RED 4 o posterior y Node.js 22 o posterior, así que comprueba antes node -v. Si has hecho la parte 1, los nodos ya están instalados.
Configura el Modbus-Client para TCP
Arrastra un nodo Modbus Write al flujo, haz doble clic y pulsa el icono del lápiz junto a Server. Se abre el Modbus-Client: la conexión que comparten todos los nodos Modbus del flujo.
| Campo | Valor | Nota |
|---|---|---|
| Type | TCP | Modbus TCP va por Ethernet |
| Host | 10.10.10.4 | Dirección IP del esclavo |
| Port | 502 | El puerto Modbus TCP de la mayoría de equipos. Usa 5020 para el servidor de prueba en Python de la guía |
| TCP Type | DEFAULT | |
| Unit-Id | 1 | Unit-Id por defecto para los nodos que dejan vacío su propio Unit-Id |
Pon un nombre a la configuración y pulsa Update. Para cada esclavo con otra dirección IP, crea un Modbus-Client más.
El Unit-Id en Modbus TCP
En Modbus TCP, la dirección IP selecciona el equipo. El Unit-Id sigue viajando en la cabecera de cada petición, y selecciona un equipo detrás de un gateway, por ejemplo un esclavo RTU detrás de un gateway Modbus TCP a RTU. Muchos equipos lo ignoran, y otros solo responden a 1 o a 255. Usa el valor del manual del equipo, y el mismo valor en todos los nodos.
La primera versión de este flujo tenía Unit-Id 1 en Modbus Write y 31 en Modbus Read. Contra nuestro esclavo simulado, que comprueba el Unit-Id, la escritura funcionó, la lectura recibió una respuesta de excepción y el panel Debug no mostró nada. El flujo de este post usa el Unit-Id 1 en los dos nodos.
Escribe un coil con Modbus Write (FC 5)
Configura el nodo Modbus Write:
- Unit-Id:
1. - FC:
FC 5: Force Single Coil. - Address:
0. El coil 00001 del manual del equipo es la dirección 0. - Quantity:
1. - Server: el Modbus-Client del paso anterior.
Añade un nodo Inject, pon en msg.payload el booleano true y conéctalo a Modbus Write. Añade un segundo nodo Inject con el booleano false. Cada clic en un nodo Inject envía una petición de escritura por Ethernet: true activa el coil y false lo desactiva.

Lee el coil con Modbus Read (FC 1)
Añade un nodo Modbus Read con el mismo Modbus-Client:
- Unit-Id:
1, el mismo que en Modbus Write. - FC:
FC 1: Read Coil Status. - Address:
0. Quantity:1. - Poll Rate:
1segundo.
Conecta un nodo Debug a la primera salida de Modbus Read, que da el array de datos, y pulsa Deploy. El panel Debug muestra un mensaje nuevo cada segundo. El array tiene 8 valores, porque la respuesta lleva los coils en bytes completos. El primer valor es el coil 0.
En nuestra prueba, después de un clic en el nodo Inject true, las lecturas siguientes mostraron un array que empieza por true, y después de un clic en false, un array que empieza por false.

Importa el flujo completo
Para saltarte los pasos manuales, abre Menu, Import, pega este flujo y pulsa Import. Después cambia la dirección IP, el puerto y el Unit-Id por los valores de tu esclavo.
[{"id":"3c784f724b6bb9da","type":"modbus-write","z":"66f823dbe170e15a","name":"","showStatusActivities":true,"showErrors":true,"showWarnings":true,"unitid":"1","dataType":"Coil","adr":"0","quantity":"1","server":"a0cecbb6e2cd25e8","emptyMsgOnFail":false,"keepMsgProperties":false,"delayOnStart":false,"startDelayTime":"","x":560,"y":220,"wires":[[],[]]},{"id":"3459adaa212c8dd3","type":"inject","z":"66f823dbe170e15a","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"true","payloadType":"bool","x":390,"y":200,"wires":[["3c784f724b6bb9da"]]},{"id":"526661ccfde56f87","type":"inject","z":"66f823dbe170e15a","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"false","payloadType":"bool","x":390,"y":240,"wires":[["3c784f724b6bb9da"]]},{"id":"f5c9e191b12f95d7","type":"modbus-read","z":"66f823dbe170e15a","name":"","topic":"","showStatusActivities":false,"logIOActivities":false,"showErrors":false,"showWarnings":true,"unitid":"1","dataType":"Coil","adr":"0","quantity":"1","rate":"1","rateUnit":"s","delayOnStart":false,"startDelayTime":"","server":"a0cecbb6e2cd25e8","useIOFile":false,"ioFile":"","useIOForPayload":false,"emptyMsgOnFail":false,"x":430,"y":320,"wires":[["3ff9cb94cb91b7e8"],[]]},{"id":"3ff9cb94cb91b7e8","type":"debug","z":"66f823dbe170e15a","name":"debug 1","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":580,"y":320,"wires":[]},{"id":"a0cecbb6e2cd25e8","type":"modbus-client","name":"","clienttype":"tcp","bufferCommands":true,"stateLogEnabled":false,"queueLogEnabled":false,"failureLogEnabled":true,"tcpHost":"10.10.10.4","tcpPort":"502","tcpType":"DEFAULT","serialPort":"/dev/ttyUSB","serialType":"RTU-BUFFERD","serialBaudrate":"9600","serialDatabits":"8","serialStopbits":"1","serialParity":"none","serialConnectionDelay":"100","serialAsciiResponseStartDelimiter":"0x3A","unit_id":"1","commandDelay":"1","clientTimeout":"1000","reconnectOnTimeout":true,"reconnectTimeout":"2000","parallelUnitIdsAllowed":true,"showWarnings":true,"showLogs":true}]
Cuando el esclavo no responde
| Síntoma | Qué comprobar |
|---|---|
| Conexión rechazada | El servidor no está en marcha, el puerto no es el correcto o un cortafuegos lo bloquea |
| Sin conexión, timeout | Dirección IP del esclavo, cable, subred. Dos Raspberry PLC nuevos en la misma red tienen los dos 10.10.10.20 en eth0: cambia uno de ellos. Compruébalo con ip a y ping |
| La escritura funciona pero Debug no muestra nada | El Unit-Id de Modbus Read. Usa el mismo Unit-Id que en Modbus Write, el del manual del equipo |
| Código de excepción 02 (dirección de datos no válida) | La dirección del coil no existe en el esclavo. Consulta la tabla de coils en el manual del esclavo |
| Código de excepción 0B (el equipo destino del gateway no responde) | Un gateway Modbus TCP a RTU no ha recibido respuesta del esclavo RTU con ese Unit-Id |
Para ver los errores en el panel Debug, activa Show Errors en los nodos Modbus Read y Modbus Write.
Qué hemos probado. Node-RED 4.1.17 sobre Node.js 22 (Linux) con node-red-contrib-modbus 5.62.1 y 5.23.3. La versión 5.62.1 se ejecutó con la librería modbus-serial 8.0.25 de npm en lugar de su propia versión modificada de esa librería. El flujo de este post, cambiando solo el host y el puerto por 127.0.0.1 y un puerto libre, se ejecutó contra un esclavo Modbus TCP simulado (pymodbus 3.16.1, Unit-Id 1). Los nodos Inject true y false activaron y desactivaron el coil 0, y Modbus Read mostró el nuevo valor en menos de un segundo. La primera versión del flujo, con Unit-Id 31 en Modbus Read, no dio datos en Debug contra el mismo esclavo. No hemos ejecutado esta actualización en un Raspberry PLC real: las dos capturas son de la primera versión del post.
Encuentra el fallo en una red Modbus RTU y TCP. En el simulador, un M-Duino 21 pregunta a cuatro esclavos Modbus RTU por RS-485 y sirve los datos por Modbus TCP. Apaga un esclavo o cambia la velocidad, y encuentra el fallo en las tramas.
Abre el simulador de red ModbusAbre la guía completa de Modbus
Preguntas frecuentes sobre Modbus TCP en Node-RED
¿Necesito el Unit-Id con Modbus TCP?
La dirección IP selecciona el equipo, pero el Unit-Id sigue estando en cada petición. Muchos equipos lo ignoran, algunos solo responden a 1 o 255, y un gateway lo usa para seleccionar el esclavo RTU que tiene detrás. Usa el valor del manual del equipo en todos los nodos.
¿Qué puerto TCP usa Modbus TCP?
El puerto estándar es el 502. Algunos equipos permiten cambiarlo, y los servidores de prueba en Linux suelen usar el 5020, porque solo root puede abrir puertos por debajo de 1024.
¿Pueden varios clientes leer el mismo esclavo Modbus TCP?
Sí, a diferencia de Modbus RTU, donde hay un solo maestro por línea. Un SCADA y el flujo de Node-RED pueden leer el mismo servidor a la vez, si el equipo acepta varias conexiones.
¿Por qué el panel Debug muestra 8 valores para un solo coil?
Porque la respuesta Modbus lleva los coils en bytes completos, y el nodo da todos los bits de esos bytes. Usa solo el primer valor.
¿Cómo hago lo mismo en Python?
Con ModbusTcpClient de pymodbus. Consulta la parte 4: Modbus TCP y Python.
Más sobre Modbus en el Raspberry PLC
- Guía de Modbus TCP y RTU para PLC con Raspberry Pi: ejemplos con Python y Node-RED
- Parte 1: Modbus RTU y Node-RED
- Parte 3: Modbus RTU y Python
- Parte 4: Modbus TCP y Python
- Socket TCP entre Python y Node-RED en un Raspberry PLC
- Caso de estudio: termómetros Modbus RTU recogidos por Modbus TCP
- Cómo elegir un controlador industrial: PLC, controlador embebido, IPC o gateway
Elige el modelo y la combinación de E/S en la familia Raspberry PLC.