Modbus RTU y Modbus TCP con Python y Node-RED
Lección 4: Modbus RTU y Modbus TCP
Modbus es el protocolo más extendido para conectar PLC con sensores, variadores, contadores de energía y sistemas SCADA. Un Raspberry PLC puede comunicarse con todos ellos: Modbus RTU por sus puertos RS-485 y Modbus TCP por Ethernet. En esta lección entenderás cómo funciona Modbus y construirás un maestro y un esclavo funcionales en Python, además de su equivalente en Node-RED.
Al final de esta lección sabrás:
- Explicar la diferencia entre Modbus RTU y Modbus TCP y cuándo usar cada uno.
- Identificar los tipos de datos de Modbus y los códigos de función más habituales.
- Implementar un maestro y un esclavo Modbus RTU y Modbus TCP con
pymodbus. - Construir un maestro Modbus en Node-RED.
1. Modbus
Modbus es un protocolo de comunicación de datos cliente/servidor (históricamente maestro/esclavo) que se usa en todo el mundo con PLC. Funciona sobre líneas serie o sobre Ethernet y permite la comunicación con muchos dispositivos en el mismo cable o red. El Raspberry PLC admite Modbus RTU sobre RS-485 y Modbus TCP sobre Ethernet.
Un comando Modbus puede ordenar a un dispositivo que:
- Cambie el valor de uno o varios registros (escribir un coil o un holding register).
- Devuelva uno o varios de sus valores (leer un coil o un holding register).
- Lea una entrada física (discrete input o input register).
1.1 Modbus RTU
Modbus RTU es la implementación más habitual. Se usa sobre líneas serie (RS-485 o RS-232) y emplea una representación binaria compacta de los datos. Cada trama está delimitada por periodos de inactividad, como se ve en la figura.

Cada dispositivo del bus tiene una dirección única (de 1 a 247; la dirección 0 es la de difusión, a la que nadie responde). Solo el maestro puede iniciar una comunicación; los esclavos solo responden a las peticiones dirigidas a ellos. Los parámetros serie (velocidad en baudios, paridad, bits de parada) deben ser idénticos en todos los dispositivos del bus, así que consulta el manual de cada uno.
1.2 Modbus TCP
Modbus TCP transporta los mismos mensajes Modbus sobre TCP/IP, normalmente en el puerto 502. Elimina el límite de direcciones del bus serie y se integra con redes Ethernet estándar, switches y sistemas SCADA. El dispositivo se identifica por su dirección IP, y el identificador de unidad se mantiene para las pasarelas hacia redes RTU.
| Modbus RTU | Modbus TCP | |
|---|---|---|
| Capa física | RS-485 / RS-232 | Ethernet |
| Direccionamiento | Dirección de esclavo 1-247 | Dirección IP y puerto |
| Uso típico | Dispositivos de campo: contadores, sensores, variadores | PLC, SCADA, pasarelas |
Nota de seguridad: Modbus no tiene autenticación ni cifrado. Mantenlo dentro de una red de control segmentada, protégelo con un firewall y no expongas nunca el puerto 502 a Internet.
2. Tipos de datos
Los datos Modbus se dividen en coils y registros. Los coils son digitales: solo pueden estar en ON (1) u OFF (0); unos representan entradas y otros salidas. Los registros son valores de 16 bits sin signo (de 0 a 65535, o de 0 a FFFF en hexadecimal). Un solo registro no puede contener números negativos, decimales ni valores superiores a 65535; para valores de 32 bits y números en coma flotante, los dispositivos usan dos registros consecutivos, y el orden de las palabras lo define el fabricante. La tabla resume los cuatro tipos de objeto.

3. Códigos de función
Modbus es un protocolo de petición/respuesta, y los servicios se definen mediante códigos de función. El código de función se codifica en un byte; cuando un cliente envía una petición, indica al servidor qué acción debe realizar. El código 0 no es válido y los valores de 128 a 255 se usan para las respuestas de excepción. Algunos códigos de función añaden códigos de subfunción para definir varias acciones. La lista de códigos:

En la práctica, los que más usarás son: FC 1 (leer coils), FC 2 (leer discrete inputs), FC 3 (leer holding registers), FC 4 (leer input registers), FC 5 (escribir un coil), FC 6 (escribir un registro), FC 15 (escribir varios coils) y FC 16 (escribir varios registros).
4. Software
Industrial Shields no ofrece una librería Modbus propia porque existen librerías completas compatibles con el Raspberry PLC. Recomendamos pymodbus, que implementa Modbus RTU y Modbus TCP en Python. El artículo sobre pymodbus con Raspberry PLC explica los fundamentos con más detalle.
La API de pymodbus cambia entre versiones principales, así que los ejemplos de esta lección están escritos para la versión 3.6.9. Instálala en un entorno virtual:
python3 -m venv ~/modbus-env
. ~/modbus-env/bin/activate
pip install pymodbus==3.6.9 pyserial5. Ejemplos
5.1 Modbus RTU con Python
Este ejemplo muestra la comunicación maestro/esclavo con pymodbus. Necesitas dos Raspberry PLC, uno como maestro y otro como esclavo. Antes de ejecutar el código, alimenta los dos PLC y conecta sus puertos RS-485 (A+ con A+ y B- con B-). La figura muestra los puertos de cada versión de Raspberry PLC. Los ejemplos usan el puerto ttySC0.

Esclavo (prográmalo en el primer Raspberry PLC):
from pymodbus.server import StartSerialServer
from pymodbus.transaction import ModbusRtuFramer
from pymodbus.datastore import (
ModbusServerContext,
ModbusSequentialDataBlock,
ModbusSlaveContext
)
# Data blocks for the four Modbus object types
store = ModbusSlaveContext(
di=ModbusSequentialDataBlock(0, [0x0] * 1000),
co=ModbusSequentialDataBlock(0, [0x0] * 1000),
hr=ModbusSequentialDataBlock(0, [0x0] * 1000),
ir=ModbusSequentialDataBlock(0, [0x0] * 1000))
context = ModbusServerContext(slaves=store, single=True)
# Start the Modbus RTU server on a serial port
# Here we use the /dev/ttySC0 port of the Raspberry PLC
StartSerialServer(context=context,
port='/dev/ttySC0',
framer=ModbusRtuFramer,
baudrate=9600)Maestro (prográmalo en el segundo Raspberry PLC):
from pymodbus.client import ModbusSerialClient
from pymodbus.transaction import ModbusRtuFramer
# Create a Modbus RTU client
client = ModbusSerialClient(port='/dev/ttySC0',
framer=ModbusRtuFramer,
baudrate=9600,
timeout=1)
if not client.connect():
raise SystemExit("Could not open the serial port")
# Slave address (1 to 247; 0 is the broadcast address)
slave_address = 1
# Write 5 holding registers starting at address 0
register_address = 0
num_registers = 5
data_to_write = [10, 20, 30, 40, 50]
response = client.write_registers(register_address,
data_to_write,
slave=slave_address)
if not response.isError():
print("Write successful")
else:
print("Error writing registers:", response)
# Read the 5 holding registers back
response = client.read_holding_registers(register_address,
num_registers,
slave=slave_address)
if not response.isError():
print("Read successful:", response.registers)
else:
print("Error reading registers:", response)
# Close the connection
client.close()Si la comunicación funciona, el maestro imprime:
Write successful
Read successful: [10, 20, 30, 40, 50]Si falla: comprueba la polaridad A+/B-, que ambos lados usan la misma velocidad en baudios y que ningún otro proceso usa el puerto serie. Un timeout suele indicar un problema de cableado o de parámetros serie.
5.2 Modbus RTU con Node-RED
La siguiente lección presenta Node-RED, así que, si nunca lo has usado, es mejor que pruebes este ejemplo después de la Lección 5. El flujo implementa un maestro Modbus RTU:
- Instala la paleta
node-red-contrib-modbus. Los nodos Modbus aparecen en el panel izquierdo. - Coloca los nodos y conéctalos como en la figura.
- Configura el nodo Inject para que envíe datos cada 5 segundos y escribe este código en el nodo Function:

if (context.hasOwnProperty('num')) {
context.num = context.num + 1;
} else {
context.num = 0;
}
msg.payload = context.num;
return msg;- En el nodo Modbus Write: configura el servidor con Type: Serial, Serial port:
/dev/ttySC0, Serial type: RTU y Baud rate: 9600. Configura FC 6 (Preset Single Register) en la dirección 0. - En el nodo Modbus Read: configura el mismo servidor, FC 3 (Read Holding Registers), dirección 0, cantidad 1 y un intervalo de sondeo de 5 segundos.
El flujo escribe y lee el registro 0 del esclavo. Como esclavo puedes usar el código Python del ejemplo anterior (recuerda configurar el unit ID con la dirección de esclavo que uses).
5.3 Modbus TCP con Python
Esta prueba usa la misma lógica que el ejemplo RTU, sobre Ethernet. Necesitas dos Raspberry PLC y 3 cables Ethernet: cada PLC se conecta a un PC (para cargar el código) y los dos PLC se conectan entre sí. Los dos puertos Ethernet se comportan de forma distinta, así que conecta ambos PLC a través del conector Ethernet superior.
Esclavo: crea un servidor TCP en la dirección y el puerto que configures. Para las pruebas usa un puerto superior a 1024 (por ejemplo 5020), porque el puerto 502 requiere permisos de administrador:
from pymodbus.server import StartTcpServer
from pymodbus.datastore import (
ModbusServerContext,
ModbusSequentialDataBlock,
ModbusSlaveContext
)
store = ModbusSlaveContext(
di=ModbusSequentialDataBlock(0, [0x0] * 1000),
co=ModbusSequentialDataBlock(0, [0x0] * 1000),
hr=ModbusSequentialDataBlock(0, [0x0] * 1000),
ir=ModbusSequentialDataBlock(0, [0x0] * 1000))
context = ModbusServerContext(slaves=store, single=True)
# Change the address and port to match your network
StartTcpServer(context=context, address=("10.10.10.20", 5020))Maestro: escribe y lee algunos registros en el esclavo. Configura el host y el puerto de tu esclavo:
from pymodbus.client import ModbusTcpClient
# Change the host and port to match your slave
client = ModbusTcpClient(host='10.10.10.20', port=5020, timeout=3)
if not client.connect():
raise SystemExit("Could not connect to the slave")
slave_address = 1
register_address = 0
num_registers = 5
data_to_write = [10, 20, 30, 40, 50]
response = client.write_registers(register_address, data_to_write, slave=slave_address)
if not response.isError():
print("Write successful")
else:
print("Error writing registers:", response)
response = client.read_holding_registers(register_address, num_registers, slave=slave_address)
if not response.isError():
print("Read successful:", response.registers)
else:
print("Error reading registers:", response)
client.close()Recuerda que los dos PLC deben tener direcciones IP en la misma red (por ejemplo 10.10.10.20 y 10.10.10.21 con máscara 255.255.255.0).
5.4 Modbus TCP con Node-RED
Para usar Modbus TCP en Node-RED, toma el flujo RTU y cambia el servidor de los nodos Modbus Write y Modbus Read a Type: TCP con la dirección IP y el puerto de tu esclavo. Puedes usar el esclavo TCP en Python de arriba.
Siguiente paso
La siguiente lección parte de aquí para crear flujos visuales y dashboards en Node-RED. Para ver qué Raspberry PLC encaja en tu red Modbus, visita la gama Raspberry PLC en www.industrialshields.com.
No hay comentarios por ahora.