Uso de un script python en Node-RED para controlar un PLC Raspberry

Aprende a utilizar el nodo exec para ejecutar scripts python
2 de febrero de 2023 por
Uso de un script python en Node-RED para controlar un PLC Raspberry
Boot & Work Corp. S.L., Bernat Brunet Pedra

Introducción

La mejor interfaz de programación para trabajar con Raspberry PLC es Node-RED, pero puede ser difícil acostumbrarse a ella porque el nodo de función (uno de los nodos más utilizados) debe ser programado en JavaScript, y sin la cantidad adecuada de conocimientos puede ser complicado acostumbrarse a ella. 

Por suerte, existe un nodo llamado "nodo exec" que puede ejecutar comandos simulando un CLI. En este post vamos a construir un flujo simple en Node-RED con la ayuda de un script en python para controlar algunas salidas basadas en una entrada analógica.


Requisitos


Objetivo del programa

La función del programa será leer una entrada analógica del PLC y compararla con un valor umbral. Dependiendo del resultado, el bajo seguirá solicitando el valor de entrada hasta que alcance el límite. Entonces, un pin digital se encenderá con el fin de notificar al usuario.

Mientras se ejecuta el programa y la comparación aún no se ha hecho realidad, el flujo también llevará la cuenta de cuántos intentos se han realizado desde el inicio y encenderá algunas salidas digitales para que el programa sea más dinámico. Simulación de una pantalla de carga personalizada. 

El objetivo, como se ha mencionado anteriormente, es mostrar lo fácil que puede ser utilizar cualquier lenguaje de programación en Node-RED y evitar en la medida de lo posible el uso de JavaScript, que puede ser difícil de aprender para los nuevos usuarios.


Construir el flujo

Después de conectarte al PLC Raspberry a través de SSH o similar, abre Node-RED con:

$ node-red-start

Y apunte un navegador a https://localhost:1880/


Ejecución del script python

Empieza arrastrando un nodo de inyección. Conéctalo a un nodo de función, luego a un nodo de lectura analógica de Industrial Shields y, por último, a un nodo de ejecución:


Estos nodos deben estar correctamente configurados. El nodo de función no será modificado ahora, pero lo usaremos más adelante. El nodo de lectura analógica debe tener tu modelo y la entrada con la que quieres trabajar. En nuestro caso, utilizaremos una Raspberry PLC 58+ y la entrada I0.7:


El nodo exec se configurará así:


Primero, la ruta al script python (en nuestro caso, /home/pi/p.py). Recuerda hacer clic en la casilla Append para que podamos pasar argumentos al script.

Ahora, cada vez que pulsemos el nodo inject, se ejecutará el script python con un argumento adicional, que será el valor de la entrada analógica.


Script en Python

El script python será muy sencillo, ya que esta parte del post es sólo para fines de demostración del nodo exec.

#!/usr/bin/env python3
import sys
print(int(sys.argv[1])) 

Tras añadir un nodo de depuración conectado a la salida stdout del nodo exec y hacer clic en el nodo inject, verás cómo se imprime el valor de la entrada en la pantalla de depuración:


El valor puede fluctuar un poco, pero como la entrada no está conectada, se mantiene en 0.


Contar los intentos

Para contar cuántos intentos se han hecho, necesitaremos una variable para recordar el número. Como el contador necesita ser actualizado cada vez que ejecutamos el script, conecta un nodo de función después del nodo exec, reemplazando el antiguo nodo debug.

Nota: El objetivo del post era evitar el uso del lenguaje JavaScript, pero, como se requiere recordar variables antiguas, este trabajo no se puede hacer en el mismo script python o con uno nuevo sin crear un archivo adicional para guardar la variable.

El código del nodo de función será el siguiente:

var tries = flow.get('tries') || 0;
flow.set('tries', tries + 1);
node.warn("Number of tries: " + (tries + 1));
if (parseInt(msg.payload) == 0) {
    return msg;
}
else {
    node.warn("Threshold surpassed with a total of tries: " + (tries + 1));
}

Aquí, inicializamos una variable llamada "intentos", que mantendrá la cuenta del total de intentos hasta que el valor de entrada supere un umbral (llegaremos a eso más adelante). Entonces, guardamos la variable de nuevo pero añadiendo 1 e imprimimos en la ventana de depuración el número total de intentos. Por último, comprobamos si el script python ha devuelto 0 o 1, dependiendo del resultado de la comparación.

Para restablecer esta variable, se utilizará el primer nodo de función:

flow.set('tries', 0);
return msg;

Con esta modificación, al hacer clic en el nodo de inyección, la variable "tries" se restablecerá. Por último, vamos a añadir un valor umbral en el script python. El script se ejecutará infinitamente cada segundo hasta que el valor de entrada llegue al límite que establecimos.

#!/usr/bin/env python3
import sys
import time
i = int(sys.argv[1])
threshold = 1000
if (i < threshold):
    print("0")
    time.sleep(1)
else:
    print("1")

Después de hacer clic en el nodo de inyección, debería obtener algo como esto:



Con este paso, hemos sido capaces de leer una entrada analógica, enviar el valor a un script python, hacer una comparación entre éste y un umbral, devolver el valor al flujo Node-RED y hacer un bucle una y otra vez si es necesario mientras se mantiene la cuenta del total de intentos.

Este podría ser el final del post, pero vamos a dar un paso más jugando con los pines de salida digital. Este paso es sólo por razones estéticas, pero puede ser útil para proyectos que requieren una configuración de salida basada en los pines de entrada.


Características adicionales

Para empezar, vamos a modificar el último nodo de función. Lo que queremos hacer es simular un símbolo de carga utilizando los LEDs de salida del PLC Raspberry:

var tries = flow.get('tries') || 0;
flow.set('tries', tries + 1);
node.warn("Number of tries: " + (tries + 1));
if (parseInt(msg.payload) == 0) {
    msg.payload = tries % 6;
    return msg;
}
else {
    node.warn("Threshold surpassed with a total of tries: " + (tries + 1));
}

Ahora, msg.payload tomará valores de 0 a 5. Con cada número (0-4), el LED específico del PLC se encenderá. Cuando el número sea 5, todos los LEDs se apagarán y el proceso se repetirá. Esta tarea se realizará utilizando un nodo interruptor conectado justo después del segundo nodo de función.

El interruptor tendrá 6 salidas diferentes. Las salidas número 1 a 5 serán para cada salida digital en HIGH, y la número 6 para todas ellas pero en modo LOW. Echa un vistazo a la siguiente imagen:


Y el nodo de interruptor:


Código de flujo completo:

[{"id":"1ca9cbdb7a453a30","type":"inject","z":"0727cd3d0291c3a3","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":140,"y":100,"wires":[["a79d112cd32173ad"]]},{"id":"a79d112cd32173ad","type":"function","z":"0727cd3d0291c3a3","name":"function 1","func":"flow.set('tries', 0);\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":280,"y":100,"wires":[["208173a4226f1ecd"]]},{"id":"208173a4226f1ecd","type":"rpiplc-analog-read","z":"0727cd3d0291c3a3","rpiplc":"ac1bec80d88e6db9","pin":"I0.7","name":"","x":420,"y":100,"wires":[["7fea86886d62f14a","f7e313cfb4659f79"]]},{"id":"40d74a62cbe0aadc","type":"debug","z":"0727cd3d0291c3a3","name":"debug 2","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":400,"y":220,"wires":[]},{"id":"5f98e491e9b75172","type":"function","z":"0727cd3d0291c3a3","name":"function 3","func":"var tries = flow.get('tries') || 0;\n\nflow.set('tries', tries + 1);\n\nnode.warn(\"Number of tries: \" + (tries + 1));\n\nif (parseInt(msg.payload) == 0) {\n    msg.payload = tries % 6;\n    return msg;\n}\nelse {\n    node.warn(\"Threshold surpassed with a total of tries: \" + (tries + 1));\n}\n","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":400,"y":180,"wires":[["208173a4226f1ecd","75ede61acb554e43"]]},{"id":"7fea86886d62f14a","type":"exec","z":"0727cd3d0291c3a3","command":"python3 /home/pi/p.py","addpay":"payload","append":"","useSpawn":"false","timer":"","winHide":false,"oldrc":false,"name":"","x":200,"y":180,"wires":[["40d74a62cbe0aadc","5f98e491e9b75172"],[],[]]},{"id":"f7e313cfb4659f79","type":"debug","z":"0727cd3d0291c3a3","name":"debug 3","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":580,"y":80,"wires":[]},{"id":"75ede61acb554e43","type":"switch","z":"0727cd3d0291c3a3","name":"","property":"payload","propertyType":"msg","rules":[{"t":"eq","v":"0","vt":"num"},{"t":"eq","v":"1","vt":"num"},{"t":"eq","v":"2","vt":"num"},{"t":"eq","v":"3","vt":"num"},{"t":"eq","v":"4","vt":"num"},{"t":"eq","v":"5","vt":"num"}],"checkall":"false","repair":false,"outputs":6,"x":550,"y":180,"wires":[["82d25edaf09c9043"],["426f1490b7fa34ad"],["c2faa787cf4e0e7c"],["1c0f19f76a739b67"],["6882cb9a974e6f53"],["cd9e0a7da9b87c08","5f0bf1b5e0065206","f049f4a7930543b7","54b2e4aca2d3aee5","c0563b371f6f81af"]]},{"id":"426f1490b7fa34ad","type":"rpiplc-digital-write","z":"0727cd3d0291c3a3","rpiplc":"ac1bec80d88e6db9","pin":"Q0.1","value":"1","name":"","x":770,"y":140,"wires":[]},{"id":"c2faa787cf4e0e7c","type":"rpiplc-digital-write","z":"0727cd3d0291c3a3","rpiplc":"ac1bec80d88e6db9","pin":"Q0.2","value":"1","name":"","x":770,"y":180,"wires":[]},{"id":"1c0f19f76a739b67","type":"rpiplc-digital-write","z":"0727cd3d0291c3a3","rpiplc":"ac1bec80d88e6db9","pin":"Q0.3","value":"1","name":"","x":770,"y":220,"wires":[]},{"id":"6882cb9a974e6f53","type":"rpiplc-digital-write","z":"0727cd3d0291c3a3","rpiplc":"ac1bec80d88e6db9","pin":"Q0.4","value":"1","name":"","x":770,"y":260,"wires":[]},{"id":"82d25edaf09c9043","type":"rpiplc-digital-write","z":"0727cd3d0291c3a3","rpiplc":"ac1bec80d88e6db9","pin":"Q0.0","value":"1","name":"","x":770,"y":100,"wires":[]},{"id":"cd9e0a7da9b87c08","type":"rpiplc-digital-write","z":"0727cd3d0291c3a3","rpiplc":"ac1bec80d88e6db9","pin":"Q0.0","value":"0","name":"","x":760,"y":300,"wires":[]},{"id":"5f0bf1b5e0065206","type":"rpiplc-digital-write","z":"0727cd3d0291c3a3","rpiplc":"ac1bec80d88e6db9","pin":"Q0.1","value":"0","name":"","x":760,"y":340,"wires":[]},{"id":"f049f4a7930543b7","type":"rpiplc-digital-write","z":"0727cd3d0291c3a3","rpiplc":"ac1bec80d88e6db9","pin":"Q0.2","value":"0","name":"","x":760,"y":380,"wires":[]},{"id":"54b2e4aca2d3aee5","type":"rpiplc-digital-write","z":"0727cd3d0291c3a3","rpiplc":"ac1bec80d88e6db9","pin":"Q0.3","value":"0","name":"","x":760,"y":420,"wires":[]},{"id":"c0563b371f6f81af","type":"rpiplc-digital-write","z":"0727cd3d0291c3a3","rpiplc":"ac1bec80d88e6db9","pin":"Q0.4","value":"0","name":"","x":760,"y":460,"wires":[]},{"id":"ac1bec80d88e6db9","type":"rpiplc-config","model":"RPIPLC_58","name":""}]


Conclusiones

Como hemos visto, no es necesario utilizar diferentes lenguajes de programación como Python para trabajar con Node-RED, pero es posible. Además, es muy sencillo tanto llamar al script como leer la salida del mismo.

Your Dynamic Snippet will be displayed here... This message is displayed because you did not provided both a filter and a template to use.


Buscar en nuestro blog

Uso de un script python en Node-RED para controlar un PLC Raspberry
Boot & Work Corp. S.L., Bernat Brunet Pedra 2 de febrero de 2023
Compartir

¿Estás buscando tu Controlador Lógico Programable ideal?

Echa un vistazo a esta comparativa de producto de varios controladores industriales basados en Arduino.

Comparamos entradas, salidas, comunicaciones y otras especificaciones con las de los equipos de otras marcas destacadas.


Industrial PLC comparison >>>