Ir al contenido

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.

Estructura de una trama Modbus RTU

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 RTUModbus TCP
Capa físicaRS-485 / RS-232Ethernet
DireccionamientoDirección de esclavo 1-247Dirección IP y puerto
Uso típicoDispositivos de campo: contadores, sensores, variadoresPLC, 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.

Tabla con los cuatro tipos de objeto Modbus: coils, discrete inputs, input registers y holding registers

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:

Lista de códigos de función Modbus

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 pyserial

5. 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.

Puertos RS-485 del Raspberry PLC según la versión de hardware

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:

  1. Instala la paleta node-red-contrib-modbus. Los nodos Modbus aparecen en el panel izquierdo.
  2. Coloca los nodos y conéctalos como en la figura.
  3. Configura el nodo Inject para que envíe datos cada 5 segundos y escribe este código en el nodo Function:

Flujo de Node-RED de un maestro Modbus RTU

if (context.hasOwnProperty('num')) {
    context.num = context.num + 1;
} else {
    context.num = 0;
}

msg.payload = context.num;

return msg;
  1. 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.
  2. 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.

Calificación
0 0

No hay comentarios por ahora.

para ser el primero en comentar.

1. Conectas un Raspberry PLC a un grupo de contadores de energía mediante una línea RS-485 de 600 m. ¿Qué variante de Modbus usas?