# Comunicación PLC Mitsubishi - Python | protocolo MC

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](https://web.archive.org/web/20211122002923/https://realpython.com/python-sockets/) nativo de **Python** para enviar las solicitudes y recibir la respuesta del PLC, ya que proporciona una [comunicación entre procesos-IPC](https://es.wikipedia.org/wiki/Comunicaci%C3%B3n_entre_procesos). Se emplea el **protocolo MC con conexión TCP/IP en formato hexadecimal de código ASCII** y se usa [GX Work2](https://www.mitsubishielectric.com/fa/products/cnt/plceng/smerit/gx_works2/index.html) para cargar la configuración al PLC.

> **Nota**: Este artículo originalmente lo redacté y publiqué el 06/12/2021 mediante [telegra.ph](https://telegra.ph/), pero ya que inicié este proyecto de blog propio en [conmagor.com](https://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](https://es.wikipedia.org/wiki/Protocolo_de_comunicaciones).

```plaintext
	┌─────────────────┐                           ┌───────────────────┐
	│                 │                           │                   │
	│                 │                           │                   │
	│                 ├───────────────────────────►                   │
	│   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:

- [Manual de usuario FX3U-ENET-L User's manual](https://web.archive.org/web/20211129191648/https://dl.mitsubishielectric.com/dl/fa/document/manual/plc_fx/jy997d38001/jy997d38001e.pdf). Para conocer el **procedimiento de comunicación** con el que trabaja.
- [Manual de entrenamiento Ethernet course(Q-series)](https://web.archive.org/web/20181123101159/http://dl.mitsubishielectric.com/dl/fa/document/manual/school_text/sh080618eng/sh080618enga.pdf). Aunque es de la *serie Q*, se utilizan los protocolos de comunicación que vamos a revisar, aparte de suministrar conceptos importantes.  

## 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*](https://web.archive.org/web/20211129173757/https://www.programmersought.com/article/77333929120/), 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](https://web.archive.org/web/20211129191648/https://dl.mitsubishielectric.com/dl/fa/document/manual/plc_fx/jy997d38001/jy997d38001e.pdf) 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](https://es.wikipedia.org/wiki/Comunicaci%C3%B3n_entre_procesos).  
- **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](https://web.archive.org/web/20181123101159/http://dl.mitsubishielectric.com/dl/fa/document/manual/school_text/sh080618eng/sh080618enga.pdf).

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](https://es.wikipedia.org/wiki/D%C3%BAplex_(telecomunicaciones)) 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**.

```plaintext
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](https://cdn.hashnode.com/res/hashnode/image/upload/v1669826130706/HUSpCROYY.png align="center")|
| --- |
| *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](https://forums.mrplc.com/).* |

| ![config_plc2.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1669826135778/6xGnLgvJb.png align="center") |
| --- |
| *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](https://forums.mrplc.com/).* |

## 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](https://www.microsoft.com/store/productId/9PJPW5LDXLZ5), o directamente desde la [web oficial de Python](https://www.python.org/downloads/).
-   **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*](https://www.digitalocean.com/community/tutorials/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](https://docs.microsoft.com/es-es/sql/reporting-services/report-server/configure-a-firewall-for-report-server-access?view=sql-server-ver15), 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](https://web.archive.org/web/20211129191648/https://dl.mitsubishielectric.com/dl/fa/document/manual/plc_fx/jy997d38001/jy997d38001e.pdf), 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](https://cdn.hashnode.com/res/hashnode/image/upload/v1669825412709/v0-ADf2Fh.png align="center") |
| --- |
| *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](https://web.archive.org/web/20211129191648/https://dl.mitsubishielectric.com/dl/fa/document/manual/plc_fx/jy997d38001/jy997d38001e.pdf)*, 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](https://cdn.hashnode.com/res/hashnode/image/upload/v1669825557201/sOvO4pVZm.PNG align="center")|
| --- |
| *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](https://cdn.hashnode.com/res/hashnode/image/upload/v1669825620490/pPzsouDF2.PNG align="center") 
   *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.

	```plaintext
	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](https://cdn.hashnode.com/res/hashnode/image/upload/v1669826387622/NlaVqBHyd.PNG align="center") 
   *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:

```plaintext
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](https://cdn.hashnode.com/res/hashnode/image/upload/v1669826497797/qSxIlgVPi.PNG align="center")|
| --- |
| *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](https://docs.python.org/3/library/socket.html), 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](https://stackoverflow.com/questions/1593946/what-is-af-inet-and-why-do-i-need-it#1594039).
- El **tipo de socket**, se usa el [socket.AF\_INET](https://stackoverflow.com/questions/35725732/what-is-the-function-of-sock-stream#35725838) 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](https://cdn.hashnode.com/res/hashnode/image/upload/v1669826529283/mMl4bz3WB.png align="center") |
| --- |
| *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`.

```python
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
- [Mitsubishi PLC communication python code](https://web.archive.org/web/20211129173757/https://www.programmersought.com/article/77333929120/)
- [Socket Programming in Python (Guide)](https://web.archive.org/web/20211122002923/https://realpython.com/python-sockets/)
- [Manual de usuario FX3U-ENET-L User's manual](https://web.archive.org/web/20211129191648/https://dl.mitsubishielectric.com/dl/fa/document/manual/plc_fx/jy997d38001/jy997d38001e.pdf)
- [Manual de entrenamiento Ethernet course(Q-series)](https://web.archive.org/web/20181123101159/http://dl.mitsubishielectric.com/dl/fa/document/manual/school_text/sh080618eng/sh080618enga.pdf)
- [GX Work2](https://www.mitsubishielectric.com/fa/products/cnt/plceng/smerit/gx_works2/index.html)
- [Foros Mr.PLC](https://forums.mrplc.com/)

#### Red
- [Protocolo de comunicación](https://es.wikipedia.org/wiki/Protocolo_de_comunicaciones).
- [Comunicación entre procesos-IPC](https://es.wikipedia.org/wiki/Comunicaci%C3%B3n_entre_procesos)
- [Semidúplex](https://es.wikipedia.org/wiki/D%C3%BAplex_(telecomunicaciones))
- [Opening a port on Linux](https://www.digitalocean.com/community/tutorials/opening-a-port-on-linux)
- [Guía de Microsoft](https://docs.microsoft.com/es-es/sql/reporting-services/report-server/configure-a-firewall-for-report-server-access?view=sql-server-ver15)


#### Python
- [Web oficial de Python](https://www.python.org/downloads/)
- [Microsoft Store](https://www.microsoft.com/store/productId/9PJPW5LDXLZ5)
- [Python Sockets](https://web.archive.org/web/20211122002923/https://realpython.com/python-sockets/)
- [Creación del socket](https://docs.python.org/3/library/socket.html)
- [Socket.AF\_INET](https://stackoverflow.com/questions/1593946/what-is-af-inet-and-why-do-i-need-it#1594039)
- [Socket.AF\_INET](https://stackoverflow.com/questions/35725732/what-is-the-function-of-sock-stream#35725838)
