Skip to main content

Command Palette

Search for a command to run...

Comunicación PLC Mitsubishi - Python | protocolo MC

Published
13 min read
Comunicación PLC Mitsubishi - Python | protocolo MC
M

Cybersecurity enthusiast and Mechanical engineer. I enjoy participating in Capture the Flag-CTF cybersecurity challenges

Se presenta la comunicación vía ethernet de un computador con un PLC Mitsubishi Electric MELSEC-F Series, modelo FX3GE, usando el módulo socket nativo de Python para enviar las solicitudes y recibir la respuesta del PLC, ya que proporciona una comunicación entre procesos-IPC. Se emplea el protocolo MC con conexión TCP/IP en formato hexadecimal de código ASCII y se usa GX Work2 para cargar la configuración al PLC.

Nota: Este artículo originalmente lo redacté y publiqué el 06/12/2021 mediante telegra.ph, pero ya que inicié este proyecto de blog propio en conmagor.com decidí revivirlo dado que ya casi es el aniversario de la publicación. Aproveché la republicación para realizar algunos ajustes.

Antes de comenzar con el código, analicemos el problema a que nos enfrentamos: En esencia se requiere enviar un mensaje, para ello se necesita de un emisor, un receptor, un medio, y un protocolo de comunicación.

    ┌─────────────────┐                           ┌───────────────────┐
    │                 │                           │                   │
    │                 │                           │                   │
    │                 ├───────────────────────────►                   │
    │   Computador    │                           │       PLC         │
    │    (Emisor)     │      Ethernet (Medio)     │    (Receptor)     │
    │                 ◄───────────────────────────┤                   │
    │                 │                           │                   │
    │                 │                           │                   │
    └─────────────────┘                           └───────────────────┘
                                            ¿Protocolo de comunicación?

A nivel de hardware se requiere del computador, el cable ethernet y el PLC; la referencia del PLC mencionada ya cuenta con un módulo para conexión ethernet, por lo tanto, no es necesario incluir uno.

El rango de protocolos de comunicación se limita a los que maneje el PLC, por lo que el siguiente paso es revisar la documentación disponible por el fabricante:

Procedimiento de comunicación

En ocasiones no se le suele prestar la debida atención a los manuales, y se comienza por hacer pruebas con librerías en distintos lenguajes y código que se encuentra en internet (lo resalto porque así fue como empecé a realizar pruebas, y de hecho encontré el artículo Mitsubishi PLC communication Python code, el cual fue de gran ayuda como marco de referencia, por lo que agradezco al autor).

Pero eventualmente me concentré en los manuales. En el capítulo 5 del manual de usuario se identifica que, el módulo de ethernet del PLC soporta:

  • Comunicación usando Fixed buffers.
  • Comunicación usando el protocolo MC (MELSEC).
  • Envío/recepción de email.
  • Conexión MELSOFT.

Pero, en la siguiente nota aclara, que solo los dos primeros permiten solicitudes de datos desde un dispositivo externo:

The following communication can be performed with an open device on other end.

  • Communication using MC protocol.
  • Sending/receiving in fixed buffer communication (procedure exists) When receiving communication request data from an external device — Section 5.1 Overview of the Communication Procedure; FX3U-ENET-L User's manual.

Veamos grosso modo las características de estas dos opciones:

  • MC Protocol (MELSEC protocol). Permite a cualquier dispositivo externo (nuestro computador personal por ejemplo) leer o escribir datos desde o hacia el PLC, o ejercer control remoto (RUN/STOP) mediante el módulo ethernet; es un modo de comunicación pasivo, de tal forma que es el dispositivo externo quien da las ordenes de lectura/escritura, y para ello puede usar cualquier lenguaje de programación que proporcione una comunicación entre procesos-IPC.
  • Fixed buffer comunication: Permite al PLC comunicarse con otros PLC o con un dispositivo externo usando los búferes fijos en la memoria búfer del módulo de ethernet, por lo que es una comunicación activa y esa es una diferencia con respecto al protocolo MC.

Si desea indagar en cada protocolo, puede revisar la sección 2.2 Types of Data Comunication Functions del manual de entrenamiento.

Para el presente caso, se decide usar el protocolo MC por el control sobre los espacios de memoria y la sencilla configuración del PLC, la cual se va a presentar a continuación, y con ello iniciar el desglose de la estructura de la solicitud que se enviará al PLC.

Protocolo MC

La transmisión de datos en el protocolo MC se realiza en modo semidúplex por lo que cuando se esté enviando un mensaje desde el computador, el PLC estará escuchando, y tendremos que esperar hasta recibir una respuesta del PLC antes de poder enviar el siguiente mensaje; la comunicación se puede usar tanto TCP/IP como UDP y son compatibles tanto en código binario como en formato hexadecimal de código ASCII.

Lado del portátil    ┌─────────┐           ┌─────────┐
                     │ Mensaje │           │ Mensaje │
─────────────────────┴─────────┼───────────┼─────────┼───────────┬──►
                               │ Respuesta │         │ Respuesta │
Lado del PLC                   └───────────┘         └───────────┘

Configuración del PLC

Se configura el PLC con GX Work2 para la comunicación mediante TCP/IP con el protocolo MC, empleando formato hexadecimal de código ASCII.

Para el ejemplo le asignaré al PLC la dirección IP 192.168.15.34 y el puerto 5000. En la figura 1 y 2 se ilustra el paso a paso de la configuración que se cargará al PLC luego de iniciar un nuevo proyecto en GX Work2.

Nota: Para el ejemplo presentado, el PLC y el PC estarán conectados directamente con el cable ethernet (o por lo menos a través de un switch de red), es decir, el PLC no estará conectado a un router, por lo que podemos asignarle cualquier dirección dentro del bloque 192.168.0.0/16, a excepción de las direcciones IP que ya estén siendo usadas.

config_plc.png
Figura 1. Configuración del PLC, definición de IP local asignada ( 3 4 5 ) ; del tipo de dado en el que se realizará la comunicación ( 6 ); y se abre la ventana para configurar el protocolo ( 7 ) — Tomado y editado de múltiples imágenes de foros Mr.PLC.
config_plc2.png
Figura 2. Se selecciona el protocolo TCP ( 8 ), el MC ( 9 ), y se asigna el puerto 5000 ( 10 ). — Tomado y editado de múltiples imágenes de foros Mr.PLC.

Configuración del PC

En el computador se requiere:

  • Instalar Python3, si es usuario de Linux, es probable que la distribución GNU/Linux que utilice ya lo tenga instalado por defecto, pero si está en Windows lo puede descargar desde la Microsoft Store, o directamente desde la web oficial de Python.
  • Habilitar el puerto de comunicación para entrada y salida de datos. En Linux se puede seguir la guía de Opening a port on Linux. Para Windows, en la configuración avanzada del firewall, debe agregar 2 REGLAS, una regla de entrada y una de salida con la misma configuración: seleccionar la comunicación TCP en el puerto que elija (5000 para este caso), y seleccionar en permitir la conexión, puede seguir la guía de Microsoft, para el paso a paso.

Estructura de la solicitud (request)

La estructura del mensaje se especifica en la sección 9.1.2 Message format and control procedure del manual de usuario, globalmente consiste en una cabecera seguida de los datos de la aplicación, para el mensaje se ignora la primera ya que en la cabecera se especifican los datos de configuración de la conexión, TCP o UDP, dirección IP, pero, eso no se escribe directamente en el mensaje, desde la configuración del socket se establece.

En la figura 3 se presenta la estructura de la lectura y escritura tanto del mensaje enviado desde el PC, hasta la de la respuesta del PLC.

general_leer_escribir.png
Figura 3. Respuesta para lectura y escritura ( 1 ) y ( 2 ) respectivamente. El Área A y B dependerán de los registros que se requieran leer y el Área C de los datos que y dónde se van a escribir. -- Tomado de manual de usuario, sección 9.1 Message Formats and Control Procedures.

Para no extenderme, el ejemplo del post se limita solo a lectura del PLC. Con la escritura notará que la principal diferencia radica en agregar los datos a escribir al final de la solicitud del mensaje, y que la respuesta del PLC se limita a indicar si se escribió exitosamente o no.

Solicitud de lectura

Para comprender la lectura y cada parte de los datos del mensaje, voy a partir del ejemplo de la pág. 9-10 del manual de usuario, en el que se leen los registros del relé interno M (el cual son marcas en memoria que se pueden usar durante el programa del PLC) en el rango de M100 hasta el M108.

read_ascii.PNG
Figura 4. (a) Partes del mensaje para lectura de datos de memoria del PLC en formato ASCII. como ejemplo se leen los registros de relé interno M desde el M100 al M108. (b) Estructura de respuesta del PLC. -- Tomado de manual de usuario, sección 9.1.3 Contents of data designation items.

En la figura 4, se aprecian las partes de los datos de la aplicación, divida según la función que cumple cada sección de bytes que se especifica en el mensaje. Recordemos que de aquí en adelante nos referiremos siempre al formato hex de código ASCII. Pasemos por cada una:

  1. Subheader 2 bytes. Indica la función que se requiere realizar, leer, escribir, testear, o controlar el PLC; en algunos casos decidir si los datos son bits o words (2 bytes). En la tabla 1 se encuentra la pareja de bytes correspondientes de acuerdo con la función, que para lectura en bits de nuestro ejemplo es la 00. Figura 5. Subheader, funciones disponibles a realizar en el PLC. -- Tomado de manual de usuario, sección 9.2 List of Commands and Functions for The MC protocol.

    subheader.PNG Figura 5. Subheader, funciones disponibles a realizar en el PLC. -- Tomado de manual de usuario, sección 9.2 List of Commands and Functions for The MC protocol.

  2. PC number 2 bytes. Valor por diseño fijo en FF.

  3. Monitoring timer 4 bytes. Periodo máximo de tiempo que se configura al módulo ethernet de espera para una solicitud y poder regresar un resultado.

    0000 < - - - Esperar indefinidamente hasta que el PLC responda
            ┌─────────────┬───────────┬────────────────┐
            │ Hexadecimal │  Decimal  │  Milisegundos  │
            ├─────────────┼───────────┼────────────────┤
            │             │           │                │
            │ 0001 - FFFF │ 1 - 65535 │ 250 - 16383750 │
            │             │           │                │
            ├─────────────┴───────────┴────────────────┤
            │ Recomendación para comunicación de datos │
            ├─────────────┬───────────┬────────────────┤
            │             │           │                │
            │ 0001 - 0028 │   1 - 40  │   250 - 10000  │
            │             │           │                │
            └─────────────┴───────────┴────────────────┘
    

    Para el ejemplo se usan 000A equivalente a 2.5 segundos.

  4. Device name 4 bytes. Tipo de registro al que vamos a realizar lectura/escritura, para el ejemplo inicial, se usa el 4D20 el cual corresponde al relé interno M. Figura 6. Device name. Código que representan el tipo de registro de memoria que se va a leer/escribir. -- Tomado de manual de usuario, sección 9.3.1 Commands and device range.

    deviceList.PNG Figura 6. Device name. Código que representan el tipo de registro de memoria que se va a leer/escribir. | Tomado de manual de usuario, sección 9.3.1 Commands and device range.

  5. Head device number 8 bytes. Indica el registro de partida para leer/escribir datos. en el ejemplo se requiere iniciar leyendo en el M100, 100 decimal son 0x64 en hex, y rellenamos con ceros el resto de los bytes 00000064.

  6. Number of device points 2 bytes. Número de registros a leer/escribir desde el punto de partida definido en el Head device number, para el ejemplo, leer entre el 64 al 72, corresponde a 8 registros, por lo que se escribe 08.

    Nota: En programación, usualmente un rango de (64, 72) se refiere al 64 incluyente y el 72 excluyente.

Los últimos dos bytes que están en 00 hacen la función de padding, es decir, se emplean para completar el último octeto de bytes, y enviar 3 octetos <==> 24 bytes. Para el ejemplo quedaría:

msg:= 00FF000A4D20000000640800

La respuesta, también tiene un subheader de 2 bytes, seguido de un código de 2 bytes que indica si la comunicación fue completa 00 o anormal 5B, si es anormal, aparecerán otros 2 bytes indicando el código del error ocurrido, como se ilustra en la figura 7.

response.PNG
Figura 7. Mensaje de lectura enviado y respuesta normal y anormal en caso de que la comunicación sea exitosa. -- Tomado de manual de usuario, sección 9.1.2 Message format and control procedure.

Ya se configuró el PLC y se comprende la estructura del mensaje (eso espero), ¡es hora de probarlo en Python!

¡Código!

Se va a enviar el mensaje escribiendo directamente con la estructura hexadecimal previa, pero se puede parsear para tener una entrada de datos cómoda.

Revisando la documentación del módulo socket, para la creación del socket, se requiere crear el objeto para el cliente que será nuestro PC con la clase socket.socket() y se ingresaran como argumentos:

  • La dirección de la familia, que para el caso es IPv4 por lo que se usa la constante socket.AF_INET.
  • El tipo de socket, se usa el socket.AF_INET ya que la comunicación es TCP/IP.

Vamos a solicitar leer words con un tiempo máximo de espera de 2.5 segundos el registro de datos D135 del PLC, el mensaje queda estructurado como en la figura 8.

msg_example.png
Figura 8. Mensaje de lectura enviado y respuesta normal y anormal en caso de que la comunicación sea exitosa. -- Tomado de manual de usuario, sección 9.1.2 Message format and control procedure.

Y para corroborarlo desde GX work2 se puede modificar el registro D135, colocando el valor 2328.

import socket

HOST = '192.168.15.34' ➊
PORT = 5000

client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) ➋
client.connect((HOST, PORT)) ➌

msg = b'01FF000A4420000000870100' ➍
to_send = bytearray(msg)

client.send(to_send) ➎
res = client.recv(1024)

print(res)
b'81002328'

En ➊ se define la IP y el puerto al cual nos comunicaremos (deben ser iguales a los definidos en la configuración del PLC). Luego en ➋ se define el cliente configurado para una comunicación IPv4 TCP/IP, con el que se enviarán y recibirán los datos. Posteriormente en ➌ se crea la conexión entre el cliente con la IP y puerto especificado para el PLC. En ➍ se prepara el mensaje en hexadecimal para lectura 01FF en el que se va a esperar un máximo de 2.5 segundos de respuesta 000A, al registro de datos D135, donde el código de dispositivo D se declara con 4420 y la dirección 135 se escribe en hexadecimal con 8 bytes 00000087; solo se va a leer un registro por lo que se agrega 01, y para finalizar se completan los 3 octetos con el padding de 00. Se finaliza en ➎ enviando el mensaje y esperando respuesta para imprimirla en pantalla y validar que la lectura sea de 2328 valor previamente escrito en la memoria del PLC desde Gx Work2

Nota: El mensaje debe usar el tipo de dato bytes, por eso se agrega el prefijo b en lugar de almacenarlo como string. ya luego se convierte a un bytearray. El prefijo de 8100 en la respuesta indica una comunicación exitosa, y efectivamente el valor de la respuesta es el esperado.

Conclusión

Se realizó lectura de registros de memoria del PLC Mitsubishi Electric MELSEC-F Series, modelo FX3GE, empleando el protocolo de comunicación MC (MELSEC). El PLC y el equipo se conectaron mediante un switch por ethernet. Se indicó la configuración necesaria del PC y PLC, así como también se detalló la estructura del mensaje de solicitud de lectura con un ejemplo empleando la librería de sockets nativa de Python3.

Mira fuera de la caja, las fronteras se disipan, conexiones nuevas aparecen, y todas ellas participan.

Referencias

PLC

Red

Python