• Modbus: guía completa del protocolo industrial, registros, direccionamiento, RTU, ASCII, TCP y ciberseguridad

    Modbus es uno de los protocolos de comunicación más extendidos en automatización industrial, supervisión de instalaciones y sistemas de control. Aunque fue diseñado hace décadas, continúa presente en controladores lógicos programables (PLC), sistemas SCADA, interfaces HMI, analizadores de redes eléctricas, contadores de energía, variadores de frecuencia, sensores, sistemas de climatización, dispositivos de entrada/salida y pasarelas industriales.

    Su permanencia se explica por un diseño sencillo: un equipo solicita una operación, otro interpreta la petición y devuelve los datos o el resultado. Sin embargo, esa aparente simplicidad oculta varias dificultades prácticas. Para trabajar correctamente con Modbus necesitamos entender qué es un registro, cómo se diferencia su número de su dirección, qué códigos de función existen, cómo se representa un valor de 32 bits, cómo se delimitan las tramas serie, qué contiene la cabecera MBAP de Modbus TCP y qué problemas de seguridad aparecen al conectar dispositivos industriales a una red IP.

    En esta guía desarrollamos el funcionamiento del protocolo desde sus fundamentos hasta la lectura real de registros en Debian, el análisis de capturas con Wireshark y las medidas de protección que debemos aplicar en entornos OT. Tomamos como punto de partida el tutorial Modbus 101 de Control Solutions y contrastamos la información con las especificaciones oficiales de Modbus Organization.

    Qué es Modbus y cómo funciona

    Modbus apareció en 1979 de la mano de Modicon, empresa posteriormente integrada en Schneider Electric. Se creó para permitir que distintos equipos de automatización intercambiaran información de forma predecible. Con el tiempo, el protocolo pasó a utilizarse sobre enlaces serie y redes Ethernet, con una especificación publicada abiertamente y sin necesidad de pagar regalías por su utilización.

    Modbus define fundamentalmente un protocolo de aplicación basado en solicitudes y respuestas. El dispositivo que necesita información envía una petición con un código de función, una dirección y los parámetros correspondientes. El equipo destinatario procesa esa operación y responde con los datos pedidos, con una confirmación de escritura o con una excepción que identifica un error.

    Podemos imaginar un contador eléctrico que expone la tensión medida en un registro y la corriente en otro. Un sistema de supervisión consulta periódicamente esos registros, interpreta sus valores y los incorpora a una gráfica. De forma similar, un sistema de control puede enviar una orden para modificar una consigna de temperatura, aunque las operaciones de escritura requieren precauciones adicionales porque pueden actuar sobre procesos físicos.

    La denominación histórica de los participantes es master/slave (maestro/esclavo), especialmente en manuales y configuraciones antiguas. En la documentación moderna se utilizan los términos cliente y servidor. En ambos casos, el principio fundamental es el mismo: el cliente inicia la transacción y el servidor proporciona la respuesta. En Modbus clásico no existe, de manera general, un mecanismo de publicación espontánea de valores equivalente al de un sistema de mensajería por eventos.

    CLIENTE / SISTEMA SCADA              SERVIDOR / DISPOSITIVO
              |                                      |
              |---- Leer 2 registros desde 0 ------>|
              |                                      |
              |<--- Valores: 100 y 200 --------------| | | |---- Leer 1 registro desde 10 ------>|
              |                                      |
              |<--- Excepción: dirección inválida ---|
              |                                      |

    La comunicación es determinista en su estructura, pero no garantiza por sí sola tiempos de respuesta fijos. Los tiempos dependen del enlace, la velocidad serie, el número de dispositivos, la carga del servidor, los tiempos de espera, las retransmisiones y, en Modbus TCP, de las condiciones de la red y de las conexiones disponibles.

    Arquitectura de una red Modbus y funciones de cada dispositivo

    En una instalación típica distinguimos varios componentes. El cliente puede ser un PLC, un SCADA, un programa de adquisición de datos o un gateway. El servidor suele ser un equipo que dispone de variables accesibles mediante Modbus. Las pasarelas convierten entre medios físicos o protocolos, mientras que los sistemas de supervisión agregan información y pueden generar históricos, alarmas o informes.

    En un bus serie Modbus RTU tradicional basado en RS-485 trabajamos con un único cliente que organiza las transacciones sobre ese segmento. Podemos conectar varios servidores con direcciones diferentes, pero no debemos conectar dos clientes que transmitan independientemente sobre el mismo bus sin un mecanismo externo de arbitraje. El protocolo serie habitual no resuelve las colisiones entre dos clientes.

    En Modbus TCP, en cambio, un servidor puede aceptar conexiones de varios clientes mediante TCP/IP. Esto no significa que todos los equipos soporten cualquier número de conexiones ni que acepten operaciones concurrentes sin limitación: el número de clientes simultáneos, las peticiones pendientes y el tratamiento de escrituras concurrentes dependen de cada implementación.

    SCADA / CLIENTE MODBUS TCP
               |
               | Ethernet / TCP 502
               |
        [Gateway TCP - RTU]
               |
               | Bus RS-485
               +------ Dispositivo RTU 1
               +------ Dispositivo RTU 2
               +------ Dispositivo RTU 3
    
    Otro cliente TCP ----> Servidor Modbus TCP nativo
                             IP: 192.168.1.50
                             Puerto: 502

    En un segmento Modbus serie, las direcciones unicast normalizadas de los servidores van de 1 a 247. La dirección 0 se reserva para difusión o broadcast; algunas órdenes de escritura pueden dirigirse simultáneamente a todos los dispositivos compatibles, y en ese caso los servidores no responden. Las direcciones 248 a 255 están reservadas para otros usos y no forman parte del rango normalizado de servidores serie, aunque algunas herramientas permitan introducirlas.

    Debemos diferenciar la cantidad de direcciones lógicas disponibles del número de equipos que admite un segmento RS-485. El límite eléctrico clásico de 32 cargas unitarias no equivale necesariamente a 32 dispositivos en todas las instalaciones. El uso de transceptores de 1/2, 1/4 o 1/8 de carga, repetidores y la topología elegida modifica la cantidad real de nodos admisible.

    Modbus RTU, Modbus ASCII y Modbus TCP: diferencias y compatibilidad

    Modbus no es sinónimo de RS-485 ni de Ethernet. El protocolo de aplicación puede transportarse mediante varias tecnologías y cada modalidad introduce reglas propias de encapsulación y delimitación de mensajes. Las implementaciones principales que nos encontraremos son Modbus RTU, Modbus ASCII y Modbus TCP.

    Característica Modbus RTU Modbus ASCII Modbus TCP
    Medio habitual RS-485 o RS-232 RS-485 o RS-232 Ethernet / IP
    Codificación Binaria Caracteres ASCII hexadecimales Binaria sobre TCP
    Delimitación Intervalos de silencio Dos puntos iniciales y CR/LF final Longitud declarada en MBAP
    Comprobación propia CRC-16 LRC Sin CRC/LRC Modbus adicional
    Direccionamiento Dirección serie Dirección serie IP y Unit Identifier
    Puerto TCP habitual No aplica No aplica 502
    Uso práctico Muy extendido en campo Principalmente equipos heredados Redes industriales IP

    Modbus RTU transmite octetos binarios y es eficiente para enlaces serie de ancho de banda limitado. Debemos establecer los mismos parámetros de comunicación en todos los participantes: velocidad, paridad, bits de datos y bits de parada.

    Modbus ASCII transforma cada octeto de datos en dos caracteres hexadecimales legibles. Esta modalidad consume más tiempo de transmisión, pero delimita sus tramas mediante caracteres explícitos y tolera ciertos escenarios serie donde el cumplimiento de los temporizadores RTU resulta difícil. No debemos confundir su menor utilización actual con su inexistencia normativa: el modo ASCII continúa descrito como opcional en la guía oficial de Modbus sobre línea serie.

    Modbus TCP utiliza una cabecera específica denominada MBAP, seguida de la unidad de datos del protocolo Modbus. No transmite una trama RTU completa dentro de TCP: no conserva el campo CRC de RTU, y la dirección del dispositivo se representa de otro modo. Esta distinción es importante al analizar capturas o programar clientes.

    También podemos encontrar productos anunciados como RTU over TCP o pasarelas serie por IP. Estos mecanismos pueden transportar bytes RTU completos a través de una conexión TCP, pero no son equivalentes automáticamente a Modbus TCP normalizado. Debemos verificar qué encapsulación espera cada extremo.

    Modelo de datos: coils, discrete inputs, input registers y holding registers

    Un dispositivo Modbus presenta un modelo lógico de cuatro clases de datos. Podemos entenderlo como cuatro espacios independientes de elementos direccionables. No son necesariamente cuatro bloques físicos de memoria; cada fabricante decide cómo representar internamente las variables, y un mismo dato puede exponerse en varios espacios o funciones.

    Objeto Tamaño Acceso lógico Función de lectura Referencia clásica
    Coil / salida discreta 1 bit Lectura y escritura 01 0xxxx
    Discrete Input / entrada discreta 1 bit Solo lectura 02 1xxxx
    Input Register / registro de entrada 16 bits Solo lectura 04 3xxxx
    Holding Register / registro de retención 16 bits Lectura y escritura en el modelo lógico 03 4xxxx

    Un coil representa un estado binario, habitualmente relacionado con una salida, un relé, una habilitación o una bandera de control. Un discrete input representa también un bit, pero su acceso Modbus está definido como lectura. Los input registers contienen palabras de 16 bits utilizadas frecuentemente para mediciones. Los holding registers representan palabras de 16 bits que pueden contener consignas, contadores, configuraciones, estados y otros valores.

    La clasificación describe las operaciones disponibles en el modelo del protocolo, no necesariamente los permisos de escritura de todos los registros reales. Un fabricante puede exponer valores de solo lectura dentro del espacio de holding registers. Por eso, no debemos asumir que un registro se puede modificar solo porque lo leamos con la función 03.

    Asimismo, una entrada física puede aparecer duplicada como input register y como holding register. Esto facilita la interoperabilidad con clientes que solamente implementan determinadas funciones, pero puede generar confusión cuando las dos referencias tienen escalas o semánticas distintas.

    Direccionamiento Modbus: por qué 40001, 1 y 0 pueden referirse al mismo registro

    Uno de los errores más habituales al integrar equipos industriales consiste en confundir la referencia documental del registro con la dirección que se transmite en la trama. El protocolo utiliza un campo de dirección de 16 bits, mientras que muchos manuales conservan una convención histórica de números precedidos por un dígito que indica el tipo de objeto.

    Notación tradicional Objeto Primera referencia Primer offset transmitido
    0xxxx Coils 00001 0
    1xxxx Discrete inputs 10001 0
    3xxxx Input registers 30001 0
    4xxxx Holding registers 40001 0

    En la notación clásica, 40001 identifica el primer holding register. Para leerlo enviamos la función 03 y la dirección hexadecimal 0x0000. El número 40001 no se transmite como un entero literal, ni tampoco se envía el dígito inicial 4: el tipo de objeto queda determinado por el código de función.

    Referencia documental: 40001
    Tipo de objeto:        Holding Register
    Función Modbus:        03 (Read Holding Registers)
    Número humano:         1
    Dirección en la PDU:   0 (0x0000)
    
    Referencia documental: 40123
    Tipo de objeto:        Holding Register
    Función Modbus:        03
    Número humano:         123
    Dirección en la PDU:   122 (0x007A)
    
    Referencia documental: 30017
    Tipo de objeto:        Input Register
    Función Modbus:        04
    Número humano:         17
    Dirección en la PDU:   16 (0x0010)

    La convención también dispone de versiones extendidas de seis dígitos, como 400001 para el primer holding register. Las formas de cinco y seis dígitos pertenecen a sistemas de referencia utilizados por fabricantes y programas; no son un campo adicional de la PDU. Una dirección Modbus de 16 bits admite offsets de 0 a 65535 en cada espacio lógico, aunque un dispositivo real solo implementará los rangos indicados en su manual.

    Hay tres situaciones que debemos identificar antes de configurar cualquier programa: el manual puede enumerar registros comenzando por 1; puede mostrar offsets comenzando por 0; o puede usar referencias heredadas como 30001 y 40001. Algunas herramientas esperan referencias humanas y realizan internamente la resta de uno, mientras otras exigen directamente offsets de protocolo.

    Como regla práctica, debemos confirmar siempre tipo de objeto, función de lectura, base de numeración, dirección inicial, longitud, tipo de dato y escala. Una lectura que comienza un registro antes o después del esperado puede devolver números aparentemente válidos, lo que convierte los errores de desplazamiento en fallos especialmente difíciles de detectar.

    El mapa de registros no es universal. Modbus estandariza cómo acceder a los datos, pero no establece que una temperatura, una tensión o un número de serie deban encontrarse en una dirección concreta. En general, necesitamos el mapa de registros proporcionado por el fabricante. Algunas implementaciones admiten identificación del dispositivo mediante funciones específicas, pero eso no sustituye el mapa completo ni garantiza un autodescubrimiento de todas las variables disponibles.

    Códigos de función Modbus y operaciones que permiten

    El código de función ocupa un octeto dentro de la PDU e indica la operación solicitada. Los códigos públicos más habituales para lecturas y escrituras básicas son los siguientes:

    Decimal Hexadecimal Operación Datos afectados Máximo por petición estándar
    01 0x01 Read Coils Leer coils 2000 bits
    02 0x02 Read Discrete Inputs Leer entradas discretas 2000 bits
    03 0x03 Read Holding Registers Leer holding registers 125 registros
    04 0x04 Read Input Registers Leer input registers 125 registros
    05 0x05 Write Single Coil Escribir un coil 1 bit
    06 0x06 Write Single Register Escribir un holding register 1 registro
    15 0x0F Write Multiple Coils Escribir varios coils 1968 bits
    16 0x10 Write Multiple Registers Escribir varios holding registers 123 registros

    Las funciones 01 y 02 devuelven bits empaquetados: en el primer octeto de datos, el bit menos significativo corresponde al primer punto solicitado. Las funciones 03 y 04 devuelven palabras de 16 bits, dos octetos por registro. Las funciones 05 y 06 modifican un único elemento, mientras que 15 y 16 permiten enviar grupos de valores consecutivos.

    Al utilizar la función 05, el estado activado se representa normalmente mediante el valor hexadecimal 0xFF00, y el desactivado mediante 0x0000. En la respuesta correcta se repiten la dirección y el valor solicitado. Para la función 06, el servidor también confirma la escritura repitiendo los campos de la solicitud. En las escrituras múltiples se devuelve normalmente la dirección inicial y el número de elementos escritos, pero no todos los valores enviados.

    Existen códigos adicionales contemplados por la especificación: 07 para leer estado de excepción en determinados equipos serie; 08 para diagnósticos serie; 11 y 12 para información de eventos de comunicación; 17 para identificación básica del servidor serie; 20 y 21 para registros de archivo; 22 para escritura con máscara; 23 para lectura y escritura combinadas de registros; 24 para colas FIFO; y 43/14 (0x2B/0x0E) para leer información de identificación del dispositivo.

    Las funciones menos comunes no están necesariamente implementadas en todos los productos. Debemos distinguir entre una función definida por el estándar y una función realmente compatible con un modelo concreto. Tampoco debemos probar funciones de escritura para averiguar si están soportadas en equipos de producción.

    Estructura de las tramas y análisis de una transacción real

    La parte común del protocolo se denomina PDU (Protocol Data Unit) y está formada por el código de función y sus datos. Al transportarla, cada modalidad añade los campos que necesita y produce una ADU (Application Data Unit). Esta separación permite entender por qué una misma operación se representa con bytes parcialmente diferentes en RTU y en TCP.

    Cómo se representa una petición Modbus RTU

    Una ADU RTU contiene dirección de servidor (1 byte), función (1 byte), datos (hasta 252 bytes) y CRC (2 bytes). Su tamaño máximo normalizado es de 256 bytes. Para leer dos holding registers desde el offset cero del servidor 1, la trama completa es:

    01 03 00 00 00 02 C4 0B
    |  |  |_____| |_____| |___|
    |  |     |       |      |
    |  |     |       |      +-- CRC-16: C4 0B (byte bajo primero)
    |  |     |       +--------- Cantidad: 2 registros
    |  |     +----------------- Dirección inicial: 0
    |  +----------------------- Función: 03
    +-------------------------- Servidor: 1

    Si los dos registros contienen los valores decimales 100 y 200, la respuesta RTU tendrá esta forma:

    01 03 04 00 64 00 C8 BA 7A
    |  |  |  |____| |____| |___|
    |  |  |     |      |     |
    |  |  |     |      |     +-- CRC-16
    |  |  |     |      +-------- Registro 2: 0x00C8 = 200
    |  |  |     +--------------- Registro 1: 0x0064 = 100
    |  |  +--------------------- Cantidad de bytes de datos: 4
    |  +------------------------ Función: 03
    +--------------------------- Servidor: 1

    Es importante observar que el segundo octeto de la respuesta es un código de función y el siguiente indica el número de bytes de datos, no el número de registros. Por ello, una lectura de dos registros produce cuatro bytes de valores.

    Cómo se representa la misma petición en Modbus TCP

    Modbus TCP incorpora una cabecera MBAP de 7 bytes seguida de la PDU. Sus campos son Transaction Identifier (2 bytes), Protocol Identifier (2 bytes), Length (2 bytes) y Unit Identifier (1 byte). El identificador de protocolo vale 0 para Modbus estándar; el campo longitud cuenta los bytes situados después de sí mismo, incluido el Unit Identifier.

    Petición TCP (12 bytes):
    00 01 00 00 00 06 01 03 00 00 00 02
    |___| |___| |___| |  |  |_____| |_____|
      |     |     |   |  |     |       |
      |     |     |   |  |     |       +-- 2 registros
      |     |     |   |  |     +---------- Offset 0
      |     |     |   |  +---------------- Función 03
      |     |     |   +------------------- Unit ID 1
      |     |     +----------------------- Longitud 6
      |     +----------------------------- Protocol ID 0
      +----------------------------------- Transaction ID 1
    
    Respuesta TCP (13 bytes):
    00 01 00 00 00 07 01 03 04 00 64 00 C8
    |___| |___| |___| |  |  |  |____| |____|
      |     |     |   |  |  |     |      |
      |     |     |   |  |  |     |      +-- Valor 200
      |     |     |   |  |  |     +--------- Valor 100
      |     |     |   |  |  +--------------- 4 bytes de datos
      |     |     |   |  +------------------ Función 03
      |     |     |   +--------------------- Unit ID 1
      |     |     +------------------------- Longitud 7
      |     +------------------------------- Protocol ID 0
      +------------------------------------- Transaction ID 1

    El servidor utiliza el Transaction Identifier para devolver una respuesta asociable a la solicitud, especialmente cuando existen varias transacciones pendientes. No podemos asumir, sin embargo, que cualquier servidor admita múltiples solicitudes simultáneas. Una conexión TCP también puede segmentar una ADU en varios segmentos: un paquete capturado no equivale necesariamente a una trama Modbus completa. El receptor debe reconstruir los datos siguiendo la longitud MBAP.

    El Unit Identifier tiene especial relevancia en gateways TCP/RTU, porque permite seleccionar el dispositivo serie que se encuentra detrás de una única dirección IP. En un servidor Modbus TCP nativo, la especificación recomienda usar 0xFF como identificador no significativo, pero en la práctica hay equipos que aceptan, ignoran o exigen otros valores, como 1. Debemos verificar la implementación concreta y no confundir Unit ID con la dirección IP.

    Cómo se representa la petición en Modbus ASCII

    En Modbus ASCII las tramas comienzan con el carácter :, continúan con parejas de caracteres hexadecimales y terminan con retorno de carro y salto de línea (CR/LF). La comprobación de integridad se realiza con un LRC. La petición equivalente a la lectura anterior sería:

    :010300000002FA\r\n
    
    :       Inicio de trama
    01      Dirección del servidor
    03      Código de función
    0000    Offset inicial
    0002    Cantidad de registros
    FA      LRC
    \r\n    Fin de trama (CR/LF)

    El valor LRC se obtiene sumando los octetos representados por el mensaje, sin incluir los delimitadores, y calculando el complemento a dos de los ocho bits menos significativos del resultado. Una trama ASCII ocupa más caracteres que su equivalente RTU, aunque puede facilitar el diagnóstico en ciertos enlaces.

    Modbus RTU en profundidad: RS-485, temporización y CRC

    Cuando trabajamos con Modbus RTU debemos distinguir tres conceptos: el protocolo Modbus, que define operaciones y datos; el formato serie, que establece la transmisión de bits y la delimitación; y la capa eléctrica, que puede utilizar RS-485 o RS-232, entre otros medios.

    RS-485 es especialmente habitual porque admite comunicaciones diferenciales y múltiples dispositivos en un mismo bus. En instalaciones de dos hilos suele emplearse una configuración half-duplex: los participantes comparten el par de comunicación y transmiten de forma alterna. RS-232 suele conectar dos extremos y no ofrece la capacidad multidrop propia de RS-485.

    Los parámetros serie deben coincidir en ambos extremos. Una configuración frecuente es 9600 baudios, 8 bits de datos, paridad par y 1 bit de parada (8E1). También encontraremos 19200, 38400, 57600 o 115200 baudios, así como 8N1 y 8N2 según fabricante. La especificación de línea serie utiliza paridad par como opción habitual y prevé dos bits de parada cuando no se emplea paridad para conservar la longitud de carácter. No conviene sustituir a ciegas los parámetros documentados por los que consideremos más comunes.

    Una característica fundamental de RTU es que no utiliza caracteres especiales para marcar el comienzo y el final: reconoce los límites de cada trama mediante intervalos de silencio. La guía oficial establece un espacio mínimo de 3,5 tiempos de carácter entre tramas. Un intervalo superior a 1,5 tiempos de carácter dentro de una trama provoca que esta se considere incompleta y se descarte. Para velocidades superiores a 19200 bit/s, la guía recomienda temporizadores fijos de 750 microsegundos y 1,750 milisegundos, respectivamente, para esos dos umbrales.

    Por ejemplo, una comunicación 9600 8E1 transmite 11 bits por carácter: un bit de inicio, ocho de datos, uno de paridad y uno de parada. Cada carácter dura aproximadamente 1,146 milisegundos y una separación de 3,5 caracteres representa alrededor de 4,01 milisegundos. Esto ayuda a explicar por qué un adaptador serie, una pasarela mal configurada o la fragmentación temporal de una transmisión pueden causar errores incluso cuando los bytes observados parecen correctos.

    El CRC-16/Modbus se calcula sobre el campo dirección, el código de función y los datos, y no sobre los bits de inicio, parada o paridad del UART. El algoritmo parte de 0xFFFF y utiliza el polinomio reflejado 0xA001. En la trama se transmite primero el byte menos significativo del CRC y después el más significativo. Para la petición 01 03 00 00 00 02 obtenemos C4 0B.

    Podemos comprobarlo de forma independiente mediante este pequeño script de Python, sin bibliotecas adicionales:

    #!/usr/bin/env python3
    
    import sys
    
    
    def fCalcularCRC(pDatos):
      vCRC = 0xFFFF
    
      for vOcteto in pDatos:
        vCRC ^= vOcteto
    
        for vIndice in range(8):
          if vCRC & 1:
            vCRC = (vCRC >> 1) ^ 0xA001
          else:
            vCRC >>= 1
    
      return bytes((vCRC & 0xFF, (vCRC >> 8) & 0xFF))
    
    
    def fPrincipal():
      aOctetos = [int(vArgumento, 16) for vArgumento in sys.argv[1:]]
      vDatos = bytes(aOctetos)
      print(fCalcularCRC(vDatos).hex(" ").upper())
    
    
    if __name__ == "__main__":
      fPrincipal()
    python3 crc_modbus.py 01 03 00 00 00 02
    # Resultado esperado: C4 0B

    En cuanto al cableado, debemos utilizar la topología de bus recomendada por los fabricantes, evitar derivaciones largas y configuraciones en estrella, instalar terminaciones adecuadas en los dos extremos físicos del segmento cuando corresponda y disponer de polarización de reposo (biasing) donde sea necesaria. Una terminación habitual se aproxima a 120 ohmios, pero debe ajustarse a la impedancia característica del cable y al diseño del sistema.

    La identificación de los conductores A/B, D+/D− o +/− no siempre es uniforme entre fabricantes. Una inversión de polaridad puede impedir la comunicación, por lo que debemos basarnos en la documentación de los dos equipos y no solamente en el color o la letra de un borne. También debemos atender las recomendaciones de referencia común, blindaje, puesta a tierra, aislamiento galvánico y protección frente a sobretensiones.

    Interpretación de los valores: enteros, signos, bits, escalas y números de 32 o 64 bits

    Una palabra Modbus contiene 16 bits, pero eso no determina por sí solo el significado de sus datos. El mismo valor binario puede representar una cantidad positiva, un entero con signo, una escala decimal, un conjunto de indicadores o una parte de un número mayor. La interpretación correcta depende del mapa de registros del dispositivo.

    Un registro de 16 bits leído como entero sin signo (uint16) admite valores de 0 a 65535. Si el fabricante lo define como entero con signo (int16) en complemento a dos, su rango es de −32768 a 32767. Así, 0xFFFF equivale a 65535 sin signo, pero a −1 con signo. No hay ningún indicador interno de Modbus que permita adivinar cuál de las dos interpretaciones corresponde.

    Es frecuente que los fabricantes utilicen factores de escala para representar decimales. Si un registro contiene 253 y su documentación indica una resolución de 0,1 °C por unidad, interpretamos 25,3 °C. Otros equipos emplean 0,01 A, 0,001 kWh o multiplicadores configurables. También debemos identificar unidades, signos, valores especiales de error y límites de validez.

    Cuando necesitamos 32 bits, lo habitual es combinar dos registros consecutivos de 16 bits. Para 64 bits normalmente se utilizan cuatro. Esto permite transportar enteros grandes, marcas temporales o valores de coma flotante IEEE 754. Modbus establece el orden big-endian de los dos bytes dentro de un registro, pero el orden de las palabras que forman un dato más ancho no es universal entre fabricantes.

    Orden habitual Secuencia de bytes para ABCD Descripción
    ABCD A B C D Palabra alta primero; bytes en orden normal
    CDAB C D A B Palabra baja primero; intercambio de registros
    BADC B A D C Intercambio de bytes dentro de cada palabra
    DCBA D C B A Inversión de palabras y bytes

    Las representaciones BADC y DCBA no siguen el orden canónico de bytes dentro de cada palabra Modbus, pero aparecen en algunas documentaciones o herramientas de conversión. Para un valor de coma flotante de 25,0, la representación IEEE 754 de 32 bits es 0x41C80000. Si el dispositivo entrega los registros 0x41C8 y 0x0000, podemos interpretar el valor con el orden ABCD; si entrega 0x0000 y 0x41C8, tendremos que intercambiar las palabras.

    También podemos interpretar valores de 8 bits empaquetados dentro de una palabra. La máscara 0xFF00 permite seleccionar el byte alto y 0x00FF el bajo. Las máscaras de bits individuales, por su parte, se construyen desplazando un 1 a la posición requerida.

    Bit  0 = 0x0001       Bit  8 = 0x0100
    Bit  1 = 0x0002       Bit  9 = 0x0200
    Bit  2 = 0x0004       Bit 10 = 0x0400
    Bit  3 = 0x0008       Bit 11 = 0x0800
    Bit  4 = 0x0010       Bit 12 = 0x1000
    Bit  5 = 0x0020       Bit 13 = 0x2000
    Bit  6 = 0x0040       Bit 14 = 0x4000
    Bit  7 = 0x0080       Bit 15 = 0x8000
    
    Byte alto: 0xFF00
    Byte bajo: 0x00FF
    
    Máscara general para el bit n: 1 << n

    Cuando un fabricante utiliza una referencia como 40001:7, normalmente está señalando el bit 7 del holding register 40001. Para conocer su estado debemos leer el registro completo con la función 03 y extraer después ese bit. No existe una función estándar que lea directamente un único bit interno de un holding register como si fuera un coil; son espacios lógicos diferentes.

    #!/usr/bin/env python3
    
    import struct
    
    
    def fInterpretarRegistro(pValor):
      vSinSigno = pValor
      vConSigno = pValor if pValor < 0x8000 else pValor - 0x10000
      vBit7 = (pValor >> 7) & 1
      vByteAlto = (pValor >> 8) & 0xFF
      vByteBajo = pValor & 0xFF
    
      print("Sin signo:", vSinSigno)
      print("Con signo:", vConSigno)
      print("Bit 7:", vBit7)
      print("Byte alto:", vByteAlto)
      print("Byte bajo:", vByteBajo)
    
    
    def fInterpretarFloat32(pRegistroAlto, pRegistroBajo):
      vDatos = struct.pack(">HH", pRegistroAlto, pRegistroBajo)
      return struct.unpack(">f", vDatos)[0]
    
    
    def fPrincipal():
      fInterpretarRegistro(0x0180)
      print("Float 32 bits:", fInterpretarFloat32(0x41C8, 0x0000))
    
    
    if __name__ == "__main__":
      fPrincipal()

    El código anterior interpreta valores que ya hemos obtenido de un dispositivo y no realiza ninguna comunicación. Si el fabricante utiliza orden de palabras CDAB, tendremos que invertir el orden de los dos registros al llamar a la función de conversión. Para datos de 64 bits aplicamos el mismo principio utilizando cuatro palabras, pero verificando además su orden y representación numérica.

    Respuestas de excepción, timeouts y diagnóstico de errores

    No todos los errores Modbus se manifiestan de la misma manera. Cuando el servidor recibe una petición válida a nivel de comunicación, pero no puede ejecutarla, puede devolver una respuesta de excepción. Cuando no recibe la petición, la descarta por un error de integridad o no puede responder, el cliente generalmente acabará alcanzando su tiempo de espera.

    Las respuestas de excepción activan el bit más significativo del código de función: código de respuesta = código solicitado + 0x80. Así, si solicitamos la función 03 y el dispositivo informa de una dirección ilegal, la respuesta contendrá 0x83 y después el código de excepción 0x02.

    Petición RTU:
    01 03 00 00 00 02 C4 0B
    
    Respuesta de excepción RTU:
    01 83 02 C0 F1
    |  |  |  |___|
    |  |  |    +-- CRC-16
    |  |  +------- Excepción 02: Illegal Data Address
    |  +---------- 0x83 = 0x03 + 0x80
    +------------- Servidor 1
    Código Nombre estándar Interpretación práctica
    01 Illegal Function Función no admitida por ese servidor
    02 Illegal Data Address Dirección o rango no disponible para esa operación
    03 Illegal Data Value Valor o estructura de un campo de petición no aceptable a nivel de protocolo
    04 Server Device Failure Fallo interno durante la ejecución
    05 Acknowledge Operación especial aceptada, todavía en procesamiento
    06 Server Device Busy El servidor está ocupado
    08 Memory Parity Error Error de consistencia en áreas de archivo especiales
    0A Gateway Path Unavailable La pasarela no pudo establecer la ruta interna
    0B Gateway Target Device Failed to Respond El dispositivo detrás del gateway no respondió

    La excepción 03 merece una precisión: no significa necesariamente que una temperatura o consigna esté fuera de su límite operativo, porque el protocolo no conoce por sí mismo la semántica de los registros. En el estándar se refiere a valores no aceptables dentro de los campos de la solicitud, por ejemplo una longitud o cantidad incorrecta. Los límites funcionales de un equipo pueden validarse mediante mecanismos adicionales del fabricante.

    También debemos distinguir entre la excepción 0B enviada por una pasarela y un timeout del propio cliente. En el primer caso existe una respuesta Modbus que informa de un fallo en el destino interno; en el segundo simplemente no hemos recibido una respuesta aceptable antes de que expire el temporizador.

    Para diagnosticar errores RTU, podemos consultar, si están soportados, los contadores y subfunciones de diagnóstico serie, revisar CRC, errores de paridad, colisiones, dirección del dispositivo y tiempo entre tramas. Para Modbus TCP, comprobamos conectividad IP, puerto, cabecera MBAP, Unit ID, códigos de función, tamaño de la respuesta y posibles cierres de conexión.

    Instalar herramientas Modbus en Debian y leer registros reales

    En Debian disponemos de herramientas para trabajar con Modbus sin instalar plataformas de automatización completas. Una de las opciones más prácticas es mbpoll, disponible en los repositorios de Debian estable. Permite consultar equipos Modbus TCP y RTU, seleccionar el tipo de dato y controlar los parámetros de comunicación. Para capturas podemos utilizar tcpdump y Wireshark o tshark.

    sudo apt update
    sudo apt install mbpoll tcpdump tshark
    
    # Comprobar las opciones disponibles
    mbpoll -h

    Supongamos que tenemos un servidor Modbus TCP de laboratorio en 192.168.1.50, puerto 502, con Unit ID 1. Para leer dos holding registers desde la primera referencia humana, ejecutamos:

    mbpoll -m tcp -a 1 -t 4 -r 1 -c 2 -1 192.168.1.50

    En este comando, -m tcp selecciona el transporte; -a 1 fija el identificador de unidad; -t 4 indica el espacio de holding registers en la nomenclatura propia de mbpoll; -r 1 selecciona la primera referencia; -c 2 pide dos valores; y -1 realiza una sola consulta en lugar de repetirla indefinidamente.

    Para leer los primeros input registers utilizamos el tipo -t 3. Debemos tener cuidado: esos valores de la opción -t identifican categorías de datos de mbpoll y no coinciden directamente con los códigos de función Modbus. Por ejemplo, -t 4 utiliza la función Modbus 03, mientras que -t 3 utiliza la función Modbus 04.

    # Input registers: función Modbus 04
    mbpoll -m tcp -a 1 -t 3 -r 1 -c 4 -1 192.168.1.50
    
    # Holding registers en hexadecimal
    mbpoll -m tcp -a 1 -t 4:hex -r 1 -c 4 -1 192.168.1.50
    
    # Offset real 0, indicando explícitamente numeración base cero
    mbpoll -m tcp -a 1 -t 4 -0 -r 0 -c 2 -1 192.168.1.50

    Si necesitamos trabajar sobre un enlace RS-485, conectamos el adaptador serie correspondiente y comprobamos el nombre del dispositivo, por ejemplo /dev/ttyUSB0. La siguiente orden consulta un servidor RTU con dirección 1 a 9600 baudios, paridad par y un bit de parada:

    # Identificar el adaptador serie
    ls -l /dev/serial/by-id/
    
    # Lectura puntual de dos holding registers mediante Modbus RTU
    mbpoll -m rtu -a 1 -b 9600 -P even -s 1 -t 4 -r 1 -c 2 -1 /dev/ttyUSB0

    Si recibimos un error de acceso al puerto serie, debemos revisar los permisos del dispositivo y la pertenencia del usuario al grupo correspondiente, generalmente dialout. Si se abre el puerto pero no llega respuesta, verificamos los parámetros 8E1/8N1/8N2, la dirección del servidor, el par diferencial, la terminación y la polarización del bus.

    Las órdenes anteriores son de lectura. Las escrituras pueden cambiar estados físicos, consignas o parámetros de funcionamiento, por lo que debemos limitarlas a equipos de laboratorio o a procedimientos autorizados y documentados. Leer tampoco es completamente inocuo en todos los sistemas: algunos equipos heredados pueden saturarse por exceso de consultas, y ciertas funciones avanzadas tienen efectos particulares.

    Programar un cliente Modbus TCP en Python

    Para integrar lecturas en scripts podemos utilizar PyModbus, una biblioteca de Python que implementa clientes y servidores Modbus. En Debian podemos instalarla con Python y emplearla para obtener valores, procesar respuestas y exportar mediciones. Conviene revisar la versión: en las versiones recientes de PyModbus 3.x, el parámetro que identifica al dispositivo es device_id, mientras que en ejemplos de versiones anteriores aparece como slave.

    python3 -m pip install pymodbus --break-system-packages

    El siguiente ejemplo realiza una única lectura de holding registers, admite parámetros desde la línea de comandos, controla los errores de comunicación y cierra la conexión incluso si se produce una excepción:

    #!/usr/bin/env python3
    
    import argparse
    from pymodbus.client import ModbusTcpClient
    
    
    def fLeerRegistros(pHost, pPuerto, pUnidad, pDireccion, pCantidad):
      vCliente = ModbusTcpClient(host=pHost, port=pPuerto, timeout=3)
    
      if not vCliente.connect():
        raise ConnectionError(f"No se pudo conectar con {pHost}:{pPuerto}")
    
      try:
        vRespuesta = vCliente.read_holding_registers(
          address=pDireccion,
          count=pCantidad,
          device_id=pUnidad
        )
    
        if vRespuesta.isError():
          raise RuntimeError(f"Respuesta Modbus de error: {vRespuesta}")
    
        for vIndice, vValor in enumerate(vRespuesta.registers):
          print(f"Offset {pDireccion + vIndice}: {vValor} (0x{vValor:04X})")
    
      finally:
        vCliente.close()
    
    
    def fPrincipal():
      vAnalizador = argparse.ArgumentParser(description="Lectura Modbus TCP")
      vAnalizador.add_argument("host")
      vAnalizador.add_argument("--puerto", type=int, default=502)
      vAnalizador.add_argument("--unidad", type=int, default=1)
      vAnalizador.add_argument("--direccion", type=int, default=0)
      vAnalizador.add_argument("--cantidad", type=int, default=2)
      vArgumentos = vAnalizador.parse_args()
    
      fLeerRegistros(
        vArgumentos.host,
        vArgumentos.puerto,
        vArgumentos.unidad,
        vArgumentos.direccion,
        vArgumentos.cantidad
      )
    
    
    if __name__ == "__main__":
      fPrincipal()
    python3 lector_modbus_tcp.py 192.168.1.50 --unidad 1 --direccion 0 --cantidad 2

    Este ejemplo utiliza offsets base cero, por lo que la dirección 0 corresponde a la primera referencia del tipo de registro solicitado. Si cambiamos de PyModbus 3.11 a otra rama de la biblioteca, debemos comprobar la documentación de esa versión antes de modificar nombres de argumentos. También podemos construir clientes serie con ModbusSerialClient e indicar expresamente la interfaz y los parámetros RTU.

    En un sistema de adquisición permanente podemos ampliar esta base con temporizadores de sondeo, reconexión controlada, un inventario de mapas de registros, almacenamiento en una base de datos de series temporales y validación de rangos. Para evitar sobrecargas, resulta preferible agrupar registros consecutivos compatibles en una misma lectura y respetar los límites de los dispositivos, en lugar de realizar cientos de peticiones aisladas sin planificación.

    Capturar y analizar tráfico Modbus con tcpdump, tshark y Wireshark

    Una captura de paquetes nos permite confirmar las direcciones consultadas, los códigos de función utilizados, los tiempos de respuesta, las conexiones inesperadas y las operaciones de escritura. Modbus TCP sin cifrar es especialmente sencillo de interpretar porque sus campos de aplicación viajan visibles en la conexión TCP.

    En un entorno autorizado, podemos capturar el tráfico de Modbus TCP de una interfaz y guardarlo para inspección posterior:

    sudo tcpdump -i enp3s0 -nn -s 0 -w modbus.pcap 'tcp port 502'
    
    # Mostrar paquetes Modbus TCP de la captura
    tshark -r modbus.pcap -Y 'mbtcp'
    
    # Mostrar únicamente operaciones de lectura de holding registers
    tshark -r modbus.pcap -Y 'modbus.func_code == 3'
    
    # Mostrar operaciones de escritura habituales
    tshark -r modbus.pcap -Y 'modbus.func_code == 5 || modbus.func_code == 6 || modbus.func_code == 15 || modbus.func_code == 16'

    En Wireshark podemos utilizar los filtros mbtcp, modbus.func_code, modbus.reference_num, modbus.exception_code y mbtcp.trans_id para localizar transacciones. La denominación exacta de algunos campos puede variar entre versiones del analizador.

    Al revisar una conversación conviene comprobar si el cliente está leyendo el offset correcto, si la respuesta incluye la cantidad de bytes esperada, si aparecen excepciones y si la duración de las transacciones aumenta con el tiempo. Cuando un servicio Modbus utiliza un puerto no estándar, puede que tengamos que indicar manualmente al analizador que interprete esa conversación como Modbus TCP.

    Para observar Modbus RTU necesitamos acceso al enlace serie mediante un analizador, un capturador o una herramienta capaz de registrar los octetos y sus marcas temporales. Una captura de bytes sin información temporal puede ser insuficiente para diagnosticar errores relacionados con las separaciones de 1,5 y 3,5 caracteres.

    Gateways, registro histórico de datos, alarmas e integración con otros protocolos

    Una pasarela Modbus puede realizar tareas muy distintas. Un gateway Modbus TCP/RTU adapta las solicitudes y respuestas entre Ethernet y un bus serie, utilizando el Unit Identifier para seleccionar el destino cuando corresponde. Otro tipo de dispositivo puede convertir objetos de Modbus a BACnet, KNX, LonWorks o SNMP, o exponer mediante Modbus variables obtenidas de equipos que utilizan protocolos diferentes.

    No todas las pasarelas son simples convertidores de tramas. Algunas incorporan tablas de mapeo, escalado, intercambio de palabras, cachés, agregación de lecturas y traducción de tipos de datos. Otras emulan la disposición de registros de un equipo antiguo para permitir su sustitución sin tener que reprogramar todos los clientes existentes.

    Debemos entender las limitaciones de estos diseños. Una lectura TCP puede tardar más de lo esperado si se traduce a varias consultas RTU secuenciales. Si varios clientes TCP consultan el mismo bus serie, la pasarela tendrá que organizar las solicitudes. Un error de conexión TCP, un timeout del dispositivo serie y una excepción 0B pueden indicar fallos diferentes, aunque a nivel de supervisión se perciban como la misma pérdida de dato.

    Cuando necesitamos almacenar históricos, una aplicación puede consultar periódicamente los registros y registrar valor, marca temporal, unidad, calidad de lectura e identificador del dispositivo. Los datos pueden exportarse a CSV, almacenarse en una base de datos o integrarse en sistemas de visualización. Conviene conservar también los fallos de consulta, ya que interpolar silenciosamente una medición ausente puede ocultar un problema de comunicación.

    Para generar alarmas podemos establecer umbrales, reglas de persistencia, histéresis y estados de fallo. No debemos confundir una alarma creada por el sistema de supervisión con una notificación enviada espontáneamente por Modbus: en el modelo clásico, la supervisión detecta el cambio después de consultar los datos. Esta diferencia condiciona la frecuencia de sondeo y el tiempo máximo de detección.

    Ciberseguridad de Modbus: riesgos reales en redes industriales

    La seguridad es una de las principales limitaciones de Modbus clásico. Modbus TCP tradicional no incorpora autenticación de clientes, autorización criptográfica por operación ni cifrado de la carga útil. Que un dispositivo responda por TCP en el puerto 502 no significa que verifique quién está solicitando información o modificando registros. Las protecciones disponibles dependen de la implementación y de los controles de red y sistema situados a su alrededor.

    Este diseño responde al contexto histórico de las redes industriales, que originalmente solían encontrarse físicamente aisladas y priorizaban simplicidad e interoperabilidad. Actualmente, las pasarelas IP, el acceso remoto, las integraciones IT/OT y las plataformas de monitorización hacen que esa confianza implícita resulte insuficiente.

    Entre los riesgos que debemos contemplar se encuentran la lectura no autorizada de variables de proceso, la manipulación de consignas y salidas, la suplantación de respuestas, la alteración del tráfico en tránsito, la pérdida de disponibilidad por exceso de conexiones o consultas y las configuraciones inseguras de los gateways. En un entorno OT, una escritura aparentemente pequeña puede modificar el comportamiento físico de máquinas, instalaciones eléctricas o sistemas de climatización.

    Para reducir la exposición debemos combinar controles técnicos y operativos:

    • Inventariar clientes, servidores, direcciones IP, Unit IDs, pasarelas, mapas de registros y funciones realmente necesarias.
    • Segmentar los sistemas OT en redes o zonas separadas, controlando los flujos entre zonas mediante firewalls y reglas explícitas.
    • Restringir TCP/502 a los clientes y servidores autorizados; evitar publicar dispositivos Modbus directamente en Internet.
    • Separar la supervisión del control cuando sea viable, permitiendo solo las operaciones de lectura necesarias y aplicando control granular sobre escrituras mediante pasarelas o firewalls industriales compatibles.
    • Proteger el acceso remoto mediante soluciones con autenticación robusta, registro de sesiones y mecanismos criptográficos apropiados, sin asumir que una conexión VPN elimina todos los riesgos internos.
    • Monitorizar el tráfico para detectar clientes desconocidos, códigos de función de escritura inesperados, cambios de frecuencia y errores anómalos.
    • Aplicar gestión de cambios, copias de configuración, procedimientos de recuperación y pruebas de compatibilidad antes de modificar sistemas de producción.
    • Priorizar la disponibilidad y la seguridad física al programar auditorías: el sondeo intensivo y las pruebas activas deben validarse según los requisitos del proceso industrial.

    La guía NIST SP 800-82 Rev. 3 desarrolla principios de defensa en profundidad, segmentación, supervisión y gestión de riesgos específicos de tecnologías operacionales. Estas medidas tienen que adaptarse a las necesidades de disponibilidad y seguridad del proceso, no aplicarse mecánicamente como si los PLC fueran simples servidores de oficina.

    Para nuevos equipos y arquitecturas compatibles, existe Modbus Security, una variante normalizada que transporta Modbus mediante TLS, incorpora autenticación mutua basada en certificados X.509v3 y permite transportar información de roles para mecanismos de autorización. Su puerto registrado es TCP/802, distinto del TCP/502 tradicional. Sin embargo, que exista el estándar no implica que todos los dispositivos Modbus lo implementen. Debemos verificar compatibilidad, versiones de TLS, certificados, políticas de acceso y requisitos del fabricante.

    La segmentación, los controles de acceso y la monitorización siguen siendo necesarios incluso cuando la comunicación está cifrada. Un canal autenticado puede proteger frente a determinadas manipulaciones de red, pero no evita por sí mismo que un cliente legítimo, comprometido o mal configurado envíe órdenes de control incorrectas.

    Problemas frecuentes al implementar Modbus y cómo identificarlos

    Síntoma Posibles causas Comprobación recomendada
    No recibimos respuesta RTU Velocidad, paridad, dirección, polaridad A/B o cableado incorrectos Revisar configuración serie, adaptador y bus
    CRC inválido o tramas intermitentes Ruido, terminación, polarización, colisiones o temporización Inspeccionar señal y marcas temporales
    TCP conecta pero Modbus no responde Unit ID no aceptado, recurso agotado, gateway o función incorrecta Revisar cabecera MBAP, límites y mapa del equipo
    Excepción 01 Función no implementada Consultar lista de funciones admitidas
    Excepción 02 Offset inválido, tipo de objeto incorrecto o lectura fuera de rango Revisar base cero/uno y rango solicitado
    Recibimos valores absurdos Escala, signo, orden de palabras o tipo numérico incorrectos Revisar unidades, mapa y endianness
    Varias lecturas funcionan por separado, pero no agrupadas Huecos no implementados o límites internos del dispositivo Reducir tamaño y agrupar solo rangos compatibles
    El gateway informa de excepción 0B Servidor serie de destino sin respuesta Verificar enlace y dirección detrás de la pasarela
    Los datos históricos tienen huecos Time out, saturación, reinicios o periodos de muestreo demasiado agresivos Registrar errores, tiempos y calidad del dato
    Dos clientes RTU interrumpen las comunicaciones Acceso simultáneo sin arbitraje al mismo bus Consolidar el sondeo mediante un cliente o gateway

    La primera decisión ante un fallo debe ser identificar en qué capa aparece: enlace eléctrico, configuración serie, comunicación TCP, encapsulación Modbus, direccionamiento, función o interpretación de valores. Cambiar simultáneamente varios parámetros suele impedir que sepamos cuál de ellos solucionó el problema y puede introducir errores nuevos.

    Como ejemplo, si conseguimos leer un registro pero recibimos una excepción al pedir cinco consecutivos, no deberíamos asumir automáticamente que la red falla. Es posible que uno de esos offsets no exista. En cambio, si todas las peticiones finalizan por timeout y estamos utilizando RS-485, conviene revisar primero la interfaz física y los parámetros de comunicación, antes de modificar el tipo de datos.

    Documentación técnica y especificaciones de referencia

    Para desarrollar clientes, integrar equipos o contrastar diferencias entre fabricantes, podemos consultar los siguientes documentos y proyectos. La especificación de aplicación define funciones, excepciones y modelo de datos; la guía serie define RTU, ASCII y comportamiento de la comunicación serie; y la guía TCP describe la cabecera MBAP, el transporte y la interacción entre clientes, servidores y pasarelas.

    Deja una respuesta