Search

Adquiriendo datos con Python y dispositivos ModBus

Vamos a empezar el año cambiando de tercio. Hoy vamos a ver cómo acceder a las lecturas de un dispositivo Modbus utilizando Python y su librería pymodbus.

En las comunicaciones industriales se puede hablar de una estructura con tres niveles:

  • En el nivel más bajo estaría el bus de campo. Es el nivel más cercano al proceso que se quiere controlar y/o monitorizar. Y es aquí donde PLCs (controladores lógicos programables), equipos de medida, y otros pequeños automatismos se integran para comunicarse entre sí con el fin de supervisar un determinado proceso.  A todo este conjunto de aparatitos se le llama célula de fabricación.
  • Por encima del nivel anterior estaría el nivel de LAN. Es en éste nivel dónde se comunican entre sí varias células de fabricación.
  • Por último tendríamos el nivel LAN/WAN. Sería el nivel más próximo al área de gestión. Este nivel se encarga de integrar los niveles anteriores en una estructura de planta industrial o incluso de múltiples plantas en diferentes emplazamientos. Aquí es donde se centralizaría el control y supervisión de todos los procesos a través de, por ejemplo, sistemas SCADA, y se incorporarían bases de datos.

Estos tres niveles se pueden representar en lo que se llama pirámide CIM (Computer Integrated Manufacturing).

Nosotros nos vamos a quedar en el nivel más bajo, en el bus de campo. Como he dicho antes es el nivel más sencillo y cercano al proceso a controlar. Un bus de campo es una red de comunicación entre los diferentes dispositivos que están implicados en el control y supervisión de un proceso, y que permite intercambiar órdenes y datos entre dichos dispositivos. Dentro de esta red, los dispositivos podrán ser todos pertenecientes a un mismo fabricante o a distintos, pero lo importante es que se puedan comunicar entre ellos a través de un protocolo reconocido por todos ellos, independientemente del fabricante del aparato.

Uno de los buses de campo más extendidos actualmente es MODBUS. Éste fue diseñado por la empresa Modicon en 1979 para implementarlo en sus PLCs, y está basado en una arquitectura maestro/esclavo (RTU) o cliente/servidor (TCP/IP). Realmente la denominación como bus de campo a MODBUS no es correcta, ya que MODBUS es un protocolo. Sin embargo, en la industria se suele hablar de MODBUS como un estándar de bus de campo. Voy a hacer un resumen rápido de sus características:

  • Es público y gratuito
  • El medio físico de conexión puede ser un bus RS-485 o RS-422.
  • Las velocidades de transmisión van desde los 75 a los 19200 baudios.
  • La distancia máxima entre dispositivos puede alcanzar hasta los 1200 metros sin necesidad de usar repetidores.
  • La estructura lógica es del tipo maestro-esclavo.
  • El número máximo de dispositivos es de 63 esclavos más un dispositivo maestro.
  • La codificación de datos dentro de la trama puede hacerse en modo ASCII o en RTU (Remote Transmission Unit).
  • Los campos de la trama del mensaje son los siguientes:
    • Número de dispositivo (1 byte): del 0 a 255. La dirección 0 es para mensajes broadcast.
    • Código de función (1 byte): Dependiendo de la función podemos transmitir datos u órdenes al esclavo.
    • Campo de subfunciones/datos (n bytes): Aquí van los parámetros necesarios para ejecutar la función anterior. Pueden palabras a leer o escribir, número de bits, etc…
    • Palabra de control de errores (2 bytes): En código ASCII es el CRC. Para el caso de codificación RTU el CRC se calcula mediante una fórmula.

MODBUS: Funciones básicas y códigos de operación

Para nuestro ejemplo vamos a usar una sonda de temperatura y humedad (data sheet de la sonda).

Para poder conectarme a ella y leer las temperaturas, voy a utilizar,  además de la propia sonda, un conversor de USB a RS-485, un pc con Ubuntu como SO, python y la librería pymodbus.

Lo primero que debemos hacer al conectar el conversor al puerto USB es verificar si se ha detectado. Para ello podemos listar los puertos serie ejecutando el siguiente comando en la consola:

Así podemos ver que el puerto que está utilizando el conversor es el ttyUSB0.

El nombre completo de este puerto es /dev/ttyUSB0, que corresponde al Conversor USB-serie 1. Es posible que tengas que cambiar los permisos mediante estos comandos:

Vale. Vamos a empezar a escribir nuestro código.

Lo primero que vamos a hacer es escribir una función que nos va a devolver el mapa de memoria de la sonda. ¿Qué es el mapa de memoria?. Es una tabla de direcciones en las que se puede acceder a los diferentes datos de la sonda. En un dispositivo básicamente podemos encontrar estos 4 bloques:

  • Salidas digitales (coils).
  • Entradas digitales (inputs).
  • Salidas analógicas (holding registers).
  • Entradas analógicas (input registers).

En el caso de nuestra sonda de temperatura y humedad, vamos a acceder a las direcciones de memoria de las salidas analógicas (holding registers).

Mapa de memoria Modbus

Según esto nos vamos a crear una función que devuelva un diccionario con tres claves-valor. Una para las direcciones de las salidas tipo integer, correspondientes a la configuración Modbus de la sonda. Otra para las direcciones de las salidas tipo float, correspondientes a las medidas de la sonda. Y una última para los valores por defecto de la configuración Modbus de la sonda. Nos quedaría algo tal que así:

Una vez que tenemos los distintos diccionarios definidos, vamos a crearnos un script que se conecte a la sonda, lea los valores y los guarde en una base de datos. Yo voy a usar MySQL.

Lo primero que vamos a hacer es una función para realizar la conexión a la base de datos y la inserción de los valores leídos de la sonda.

Para ello crearemos el archivo mysqlConnect.py y dentro escribiremos la siguiente función:

Ésta función recibe como argumento una lista de valores, y lo que hace es conectarse a la base de datos e insertar en una tabla esa lista de valores, creando un nuevo registro.

Bien. Vamos ahora a escribir el script principal, que será el que realiza la conexión con la sonda, la lectura de los valores, y llamará a la función insertQuery para guardarlos datos obtenidos en la base de datos MySQL.

Empezamos importando las librerías necesarias, y creando una serie de variables necesarias para realizar la conexión con la sonda.

Para la lectura de los datos, utilizaremos la función read_holding_registers.

Ésta admite como parámetros, la dirección desde la que se empieza a leer, el número de registros a leer, y el número de periférico del dispositivo al que se le pregunta, en este caso, nuestra sonda, que lleva el número de periférico 1.

Empezaremos leyendo los valores de los registros correspondientes al diccionario memo_integers, que hemos creado anteriormente, y que contiene los datos de configuración de la sonda. En este caso, solo leeremos el primer registro.

A continuación, leeremos los valores de las medidas, cuyas direcciones cogemos del diccionario memo_floats. En este caso, al ser un tipo de dato float, tenemos que leer, por un lado el registro 1 y por otro el registro 2. Necesitaremos ambos para obtener el valor . Para ello añadimos el siguiente bloque de código:

Si hacemos un print del resultado de esas lecturas, que hemos llamado rr1 y rr2, podemos ver sus valores. Por ejemplo nos vamos a fijar en la humedad absoluta:

El valor de rr1 es 23040 y el de rr2 es 16808. A partir de dichos valores, queremos obtener otro valor en formato coma flotante simple (32 bits) (IEEE-754 Floating Point).
Para obtener el valor de la humedad en dicho formato, tenemos que realizar los siguientes pasos:
1.- Construir, a partir de los dos números decimales obtenidos, un número binario de 32 bits. Para ello pasamos cada número decimal obtenido a su valor binario, y le añadimos un 0 al inicio, para tener 8 bits en cada parte. Este paso lo realizamos con las líneas de código:

El resultado es el siguiente string que representa un número binario de 32 bits:
01000001101010000101101000000000

2. Lo siguiente es pasar el string a un entero, pasándole como argumento la base que queremos utilizar, en este caso, base 2.

3. Utilizar la librería struck de python para poder poder convertir los tipos de datos obtenidos en el formato requerido:

Con lo que obtenemos un valor para la temperatura de 21.04 ºC

En el este conversor online podemos comprobar que el resultado es correcto:

FloatConverter/IEEE754.html

Los datos se van añadiendo en una lista, la cual que se pasa como argumento al final del script a la función insertQuery, que se encarga de insertar dichos valores en una tabla de la base de datos mySQL.

Este script podemos automatizarlo utilizando cron, de manera que se ejecute, por ejemplo cada 15 minutos.

Para finalizar, podemos hacernos una función sencilla para la lectura de algunos datos de temperatura guardados en mySQL, y representarlos en un gráfico:



Puedes descargarte el código de mi 

7 Comments

  • Ricardo

    12 junio, 2019 at 17:10

    Gracias por compartir tu info, estoy tratando de correr tu ejemplo del Post 17 y me da un error, que es “from pymodbus.client.sync import ModbusSerialClient as ModbusClient”.

    Puedes indicar como resolver este problema ?..

    Agradecido por tu tiempo de antemano.

    Saludos y gracias de nuevo

    Responder
  • Tikey Rivas

    19 septiembre, 2019 at 17:00

    Hola, soy nuevo en el mundo de Python y RTU Modbus, estoy buscando ayuda para poder comunicarme con un VDF.
    los primeros paso con el código los di usando el LABJACK y aunque me falta entender varias cosas pude encontrar unos totorales paso a paso para entender que pasa en cada uno de los comandos e ir armando un programa simple que me permite dependiendo de la lectura de las entradas digitales parar el código activar un contactor o leer la señal analógica.

    Pero ahora regresando al VDF estoy usando un adaptador a USB

    se que con este comando ModbusClient declaro a una variable que será como mi dispositivo el cual contara con todas las características para lectura y escritura

    en mi caso use este :

    c = ModbusClient(method=’rtu’, port=’/dev/tty.usbserial-AH05KHGO’, timeout=1, stopbits = 1, bytesize = 8, parity=’N’, baudrate= 9600)
    c.connect()

    la consola no me genera ningún tipo de error pero luego al intentar usar

    read_holding_registers

    me da errores.

    adicional al las dudas que tengo con el uso de la librería pymodbus también tengo dudas de las direcciones del VDF ya que el fabricante me facilito lo que seria el mapa de direcciones de memoria que pueden ser leídas y escritas y para que es cada una pero no me indica cuales son holding ni nada de eso, y en este aspecto quede en las nubes ya que no cuento con alguien que me pueda ayudar.

    tendrán un manual paso a paso para realizar la configuración y comunicación y expliquen para que es cada parámetro, incluso en ingles me ayudaría mucho (soluciono con el traductor)

    Responder
    • Tikey Rivas

      24 septiembre, 2019 at 00:20

      After spending a good time analyzing the instructions I was able to perform a VDF reading without indicating errors but I am not able to interpret the response or the reading I am doing poorly, since the memories I am reading are made up of several data
      example memory 1000H contains 8 data from 0001H to 0008H

      But the problem is that the code tells me
      “ReadRegisterResponse (1)”

      using the command
      “(c.read_holding_registers (address = 0x1000, starting_address = 0x0001, count = 0x0001, quantity = 0x1000, unit = 0x01)”

      for the VDF instructions stop and start and had no problems using

      “c.write_register (address = 0x1000, value = 1, unit = 0x01)”

      Responder
      • Tikey Rivas

        24 septiembre, 2019 at 12:01

        Gracias por la ayuda, el problema es más simple de lo que pensaba, todo es debido a un mal uso del las instrucciones o, a la falta de ellas.

        Como no encontré un ejemplo con este tipo de lectura no entendía bien como debía realizarlo pero tendría que ser algo de este tipo para realizar una lectura de memoria.

        DATO = client.read_holding_registers(value, address, unit=0x01)

        donde “valué” es una vector vacío
        “address” es la direction de memoria a leer
        “unit ” el dispositivo en el modbus a leer

        esto nos responde con “ReadRegisterResponse (1)”

        Lo que indica que se pudo leer correctamente un registro

        para hacer uso del dato obtenido se implementa

        DATO.registers[0]

        con “.registers[0]” se obtiene el dato guardado en la clase read_holding_registers de nombre “DATO”

        en esto ya logramos leer las memorias de un VDF y solo resta con una lista identificar a qué hace referencia cada uno de los datos que podría arrojar esta lectura.

        Código USADO para leer la moría “0x1001” de un VDF

        Direccion = 0x1001
        monitor = c.read_holding_registers(address=Direccion,starting_address=0x0001,count = 0x0001, unit=0x01)
        if not monitor.isError():
        for x in range(0,len(monitor.registers)):
        y = (monitor.registers[x])
        print (“Estado del VDF : “,end=””)
        if Direccion == 0x1001:
        if y == 1:
        print (“Forward running”)
        elif y == 2:
        print (“Reverserunning”)
        elif y == 3:
        print (“Standby”)
        elif y == 4:
        print (“Fault”)

        time.sleep(0.03)

        else:
        print (” “)
        print (“ERROR en Lectura : “,monitor.isError)

        Responder
  • Leonardo

    22 octubre, 2019 at 01:03

    Hola, como va?
    Como puedo hacer para leer datos de un registrador de energia trifasico marca circutor o schneider?. Ademas me interesaria poder registrar en un grafico los valores de Tension, corriente, etc (todos los parametros que arroja el registrador). Y luego poder graficas o disponer de los datos en una tabla.
    Tengo el conversos RS485 to USB
    Cordiales saludos
    Atte.-
    leonardo.-

    Responder

Deja un comentario

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.