tutorials:arduino:10_arduino_node-red

Arduino Node-Red

Com utilitzar node-red amb arduino:

El primer es instal·lar un bloc o funció a la paleta per utilitzar l'Arduino, el primer que he provat es el node-red-node-arduino que utilitza el firmata standard per llegir i escriure sobre els pins del arduino. I ens surt el següent bloc:

I com es pot veure a l'imatge no te molt de problema, sobretot les sortides. Amb un bloc que envia true o false active o desactive el pin-led 13. A més he fet un switch al dashboard ip_dispositi:1880/ui que també envia true o false.

Sembla fàcil, però amb les entrades, un polsador, per exemple es quan comencen els problemes. Com em varen apareixer al p5js per accedir al port serie que cal instal·lar un servidor pont sockets-port serie. En aquest cas no cal cap servidor però els problemes son al hora de programar que cal saber una mica de nodejs i com funciona el node-red. Per fi he trobat una lloc on s'explica be: http://www.steves-internet-guide.com/node-red-overview/

El primer que cal entendre son els missatges d'un node a altre. Be abans, hi nodes nomes envien missatges, que reben-transformen-envien, i que només reben.

Que es exactament el missatge: http://www.steves-internet-guide.com/node-red-message-object/

Es un objecte del nodejs. Que es un objecte en nodejs?

{myValue:123}

No te res a veure amb la programació orientada a objectes, es una etiqueta:valor

Altra peculiaritat del nodejs, els tipus de dades:(typeof 3)

undefined

null

number

boolean

string

function

Podem veure mes detalls a http://www.java2s.com/Tutorials/Javascript/Node.js_Tutorial/index.htm

Es un llenguatge una mica especial, una funció es un tipus de variable i sol utilitzar-se sempre orientat a events. Que et fa tan ràpid i optim per comunicacions, i tan optimitzat, tot ho fa al mateix fil i només executa una funció quan es necessari.

Doncs be, missatge típic del node-red es:

  • payload
  • topic
  • _msgid

Exemple: {“payload”:23,“topic”:“hola”,“_msgid”:“f5e386d2.9132b8”}

{“payload”:“F1 1 1”,“topic”:“7”,“_msgid”:“226043bc.e54adc”}

No son tots iguals, un missatge MQTT es de la següent forma:

  • payload
  • topic
  • qos
  • _msgid
  • retain

El msg ha de ser d'un tipus de JavaScript, String, Integer, Boolean, Object.

Les funcions principalment el que fan es adequar aquest missatge, hi ha funcions ja fetes com change, que el que fa es quan rep un meg.payload true el pot raduir a un integer de valor 8. Hi ha mes funcions ja fetes split, join, switch.

Però el mes interessant es que podem crear les nostres funcions escrites en JavaScript(node.js).

podem enmagatzemar el valor que hem rebut del bloc anterior:

 var payload_rebut=msg.payload;
 

i modificar-lo si es un enter podriem sumar-li 1

 
 payload_rebut++;
 msg.payload.payload;
 return msg;
 
 

Arriba el moment de provar un polsador. El firmdata-node-red només envia des del Arduino els canvis d'estat. Es ha dir no se com està el polsador en aquest moment. Però si algú el pitja passarà de 0 a 1 o millor dit de 1 a 0(pullup) obligatoria, si pose pulldown capta moltes interferencies i no deixa de rebre falses pulsacions.

Així que no podem consultar l'estat d'una entrada, el que el fa poc útil per la programació tipus autòmat, la solució es posar un modbus client al arduino, que ja era la meva idea, que consulta cad 500ms l'esta del polsador, i no fa falta ni filtre(debounce).

No funciona el pullup a la configuració de l'entrada, segurament serà que tinc una versió antiga de firmata que no ho permetria, així que perquè funcioni el millor es una resistència externa pullup

A més no te filtre (debounce), detecta dos o tres pulsacions. I per últim.

Per fer mes proves cal memoritzar l'esta de la vaiable si vull saber com està el polsador. Però un dels problemes més grans d'aquest node-red, com es defineixen les variables?

Doncs te historia. Perquè el node-red passa el msg object d'un node a altre, a la funció reemplacem el msg object però on puc emmagatzemar data entre els diferents nodes o a un mateix node. Per exemple un comptador, que compte les polsansions.

El node-red te tres tipus de variables:

context object emmagatzemament del valor dintre del node o funció.

flow object emmagatzema i pot utilitzar-se a qualssevol node del flow(pagina)

global object pot utilitzar-se a tots el nodes de tots els flows

Doncs be per unicialitzar una varable count(comptador) cal fer el següent:

 var count=context.get('count') || 0;
 

El que vol dir que la variable count es igual al valor de la variable de context 'count', i que si aquesta no existeix es 0. Clar al no definir la variable fora de funció quan entra dintre li torna a donar el valor inicial, em d'enrecordar-nos de posar al final de la funció el nou valor de la variable conunt amb un set.

 context.set('count', count);
 

Al final la funció que funciona, que compata les polsasions, i que a mes canvia el format del msg.payload , d'un integer a un string es:

var count=context.get('count') || 0;
count +=1;
msg.payload="F1 "+msg.payload+" "+count;
context.set('count',count);
return msg;

De vegades pot donar error i aquesta manera es preferible:

var count=context.get('count') ;
if (typeof count=="undefined")
count=0;

Igual podem utilitzar la de flow:

var count=flow.get('count') || 0;
flow.set('count',count);

O la global:

var count=global.get('count') || 0;
global.set('count',count);

Les dades de context, flow i globals s'enmagatzemen a la memoria. Vol dir aixó que si es reinica es perd.

A partir de la versió 0.19 podem fer que s'enmagatzemen al disc i no es perdin, cal modificar els settings(on tambe es pot canviar el port, que demani contrassenya….)

contextStorage: {
   default: "memoryOnly",
   memoryOnly: { module: 'memory' },
   file: { module: 'localfilesystem' }
},

Podem seguir utilitzant la memoria com fins ara, però si volem que ho enmagatzeme al disc cal afegir la paraula file per al get:

 context.get("count", "file");
 

i per a set:

 context.set("count", count,"file");
 

El sistema emmagatzema les variables en un JSON(JavaScrpitObjectNottation) en una carpeta anomenada context al .node-red

Resulta que he provat amb logo siemens i es molt fàcil. Cal instal·lar el node-red-contrib-s7. Com el MODBUS va actualitzant-se cada poc temps i es més fàcil la programació que amb firmata, per events, només veig quan hi ha un canvia a l'entrada però no l'estat actual. Amb el MODBUS i el protocol de siemens(molt semblant al MODBUS) se la posició actual de l'entrada encara que no tingui cap canvi.

La configuració també es molt fàcil a l'autòmat configuració ethernet, com a servidor amb el TSAP 02.00 que normalment es del HMI.

Pel que fa a la configuració al NODE_RED cal fer dos coses configurar el automat i les variables, entrades i sortides. El millor es que podem configurar directament les entrades i sortides. Així:

Per fer HMI. Es potent però una mica complica per fer HMI gràfics, el dashboard nomes deixa text, botons….

El millor es obrir el exemples i llegir la ajuda.

La wiki està plena d'exemples i informació del modbus destaquem els següents:

NO FUNCIONA modpoll, no funcionen les funcions d'escriure valors

Per això utilitze el QTModbus, http://qmodbus.sourceforge.net/, https://github.com/ed-chemnitz/qmodbus/ tot i que he hagut d'instal·lar el qt creator per compilar-lo, de fet he vist on fallava, hi ha un llibreria la libmodbus que cal compilar primer ./configure make supose que es la llibreria fonamental libmodbus, i cal compilar-la per un sistema especific(32 bits - 64 bits). De fet està al repositori apt install libmodbus i ja estaria, i el QTModbus es només un front-end però està molt bé perquè està el RTU, el ASCII i el TCP i per funcions 0x01…0x05….

Per instal·lar-lo:

Instal·lem primer el libmodbus:

cd ./3rdparty/libmodbus
autogen.sh
./configure
sudo make install 

i despres ja els qtmodbus, no cal tenir el qt-creator però si el qmake

mkdir build
cd build
qmake ..
make

El qmake ha donat un error pero el make ha funcionat. Deixe aquí la versió sense compilar.qmodbus-master-src-sensecompilar.zip i el compilat a un xubutu 20.04 a 64 bits. qmodbus-master-comp-senseqtcreator.zip

La primera prova que he fet amb el qtmodbus i la llibreria mes senzilla d'Arduino, la que vaig fer servir per al Arduino MODBUS HMI(weintek) ha funcionat.

La idea es fer servir aquesta llibreria i utilitzar el Arduino sempre com a client i poder així programar amb hmi, classic, ladder, node-red, processing, inclus android amb algun hmi-modbus-android, la resta del programa estaria al arduino. Com es faria a qualsevol automat.

Instal·lació a 32bits 18.04

He tingut problemes he hagut d'instal·lar, qt5-default no s'instal·lava i he posat el qt4-default.

sudo apt install qt4-default

Ja ha funcionat, deixe aquí compilat per no tenir-lo que compilar altre dia: qmodbus_qt4_18.04_32bits.zip

Faig una programa arduino amb 6 entrades, pins del 2-7, 6 sortides de 8 al 13 i 6 analògiques, després les podem activar i desactivar desde qualsevol hmi. Una mena de firmata però amb Modbus.

#include "modbus.h"
#include "modbusDevice.h"
#include "modbusRegBank.h"
#include "modbusSlave.h"

/*
This example code shows a quick and dirty way to get an
arduino to talk to a modbus master device with a
device ID of 1 at 9600 baud. (8-bits-1stop-senseParitat)
*/

//Setup the brewtrollers register bank
//All of the data accumulated will be stored here
modbusDevice regBank;
//Create the modbus slave protocol handler
modbusSlave slave;

  unsigned long previousMillis = 0;
  const long interval = 5000; 
  
  int ledState=0;
  int output13=0;

void setup()
{   

//Assign the modbus device ID.  
  regBank.setId(1);

/*
modbus registers follow the following format
00001-09999  Digital Outputs, A master device can read and write to these registers
10001-19999  Digital Inputs, A master device can only read the values from these registers
30001-39999  Analog Inputs, A master device can only read the values from these registers
40001-49999  Analog Outputs, A master device can read and write to these registers 

Analog values are 16 bit unsigned words stored with a range of 0-32767
Digital values are stored as bytes, a zero value is OFF and any nonzer value is ON

It is best to configure registers of like type into contiguous blocks.  this
allows for more efficient register lookup and and reduces the number of messages
required by the master to retrieve the data
*/

//Add Digital Output registers 00001-00016 to the register bank
  regBank.add(1);  //pin8
  regBank.add(2);  //pin9
  regBank.add(3);  //pin10
  regBank.add(4);  //pin11
  regBank.add(5);  //pin12
  regBank.add(6);  //pin13

//Add Digital Input registers 10001-10008 to the register bank
  regBank.add(10001);  //pin2
  regBank.add(10002);  //pin3 
  regBank.add(10003);  //pin4
  regBank.add(10004);  //pin5
  regBank.add(10005);  //pin6
  regBank.add(10006);  //pin7 

//Add Analog Input registers 30001-10010 to the register bank
  regBank.add(30001);  //pinA0
  regBank.add(30002);  //pinA1
  regBank.add(30003);  //pinA2
  regBank.add(30004);  //pinA3
  regBank.add(30005);  //pinA4
  regBank.add(30006);  //pinA5

//Add Analog Output registers 40001-40020 to the register bank
  regBank.add(40001);  //sortidaPWM-pin9
  regBank.add(40002);  //sortidaPWM-pin10
  regBank.add(40003);  //sortidaPWM-pin11
  regBank.add(40004);  //altres
  regBank.add(40005);  //
  regBank.add(40006);  //
  

/*
Assign the modbus device object to the protocol handler
This is where the protocol handler will look to read and write
register data.  Currently, a modbus slave protocol handler may
only have one device assigned to it.
*/
  slave._device = &regBank;  

// Initialize the serial port for coms at 9600 baud  
  slave.setBaud(9600);  
  //configuracio pins
  pinMode(13,OUTPUT);
  pinMode(12,OUTPUT);
  pinMode(11,OUTPUT);
  pinMode(10,OUTPUT);
  pinMode(9,OUTPUT);
  pinMode(8,OUTPUT);
  pinMode(7,INPUT);
  pinMode(6,INPUT);
  pinMode(5,INPUT);
  pinMode(4,INPUT);
  pinMode(3,INPUT);
  pinMode(2,INPUT);
  pinMode(A0,INPUT);
  pinMode(A1,INPUT);
  pinMode(A2,INPUT);
  pinMode(A3,INPUT);
  pinMode(A4,INPUT);
  pinMode(A5,INPUT);
  
  //Valors inicials, abans de la comincacio:
  regBank.set(1, 0);  
  regBank.set(2, 0);  
  regBank.set(3, 0);  
  regBank.set(4, 0); 
  regBank.set(5, 0); 
  regBank.set(6, 0); 
 
  regBank.set(10001, 0);
  regBank.set(10002, 0); 
  regBank.set(40001, 0); 
  regBank.set(10003, 0);  
  regBank.set(10004, 0);  
  regBank.set(10005, 0);  
  regBank.set(10006, 0);  
 
  
  regBank.set(30001,0);
  regBank.set(30002,0);
  regBank.set(30003,0);
  regBank.set(30004,0);
  regBank.set(30005,0);
  regBank.set(30006,0);


  regBank.set(40001,0);
  regBank.set(40002,0);
  regBank.set(40003,0);
  regBank.set(40004,0);
  regBank.set(40005,0);
  regBank.set(40006,0);
}

void loop() {
 
    slave.run(); 
  
 ///////// TEST   ///////////////////
 
    // engegar i aturar el LED desde el servidor
    // servidor WRITE SINGLE COIL (0x05)
    output13=regBank.get(6); //si comença de 0 es el 5
    if (output13<=0) digitalWrite(13,0);
     else digitalWrite(13,1);
     
    //polsador  pin7
    // servidor READ SINGLE INPUT (0x02)
    if (digitalRead(7)==0) regBank.set(10006,1);
    if (digitalRead(7)==1) regBank.set(10006,0); 
    
    
   // intermitencia a les 1-2 entrades(simulem polsadors)
   unsigned long currentMillis = millis();
   if (currentMillis - previousMillis >= interval) {
   previousMillis = currentMillis;

    if (ledState == 0) {
      ledState = 1;
    } else {
      ledState = 0;
    }
      regBank.set(10001, ledState);
      regBank.set(10002, ledState);
    }
 
 ///////// TEST ////////////////////////
  
 }

modbus_stadard_0.zip

El problema, que sol passar al modbus, que uns començen per 0(QTModbus), node-red i aquest comença per 1, però es una cosa que passa a molts modbus. De moment o puc deixar així.

La funcio es la FC5 del modbus, en principi no hi ha cap norma que ens digui com numerar les sortides. Al programa he assignat 6

PIN Arduino Modbus desde 0 Modbus desde 1
8 0 1
9 1 2
10 2 3
11 3 4
12 4 5
13 5 6

El tros de condi mes importat,

    slave.run(); 
  
    // engegar i aturar el LED desde el servidor
    // servidor WRITE SINGLE COIL (0x05)
    output13=regBank.get(6); //si comença de 0 es el 5
    if (output13<=0) digitalWrite(13,0);
     else digitalWrite(13,1);

Per llegir utilitzem la funció FC2, a l'arduino la part més important es la:

    //polsador  pin7
    // servidor READ SINGLE INPUT (0x02)
    if (digitalRead(7)==0) regBank.set(10006,1);
    if (digitalRead(7)==1) regBank.set(10006,0); 
 

Al node-red, a l'ultima part del video veig que no funciona be quan canvien el temps entre crides pull rate he fet proves i funciona amb un interval mínim de 1550 ms.

Al node-red, a l'ultima part del video veig que no funciona be quan canvien el temps entre crides pull rate he fet proves i funciona amb un interval mínim de 1550 ms.

Però ha de ser un problema del node-red, perquè amb el QTModmus, no tinc la possiblitat de posar un poll rate, però puc polsar ràpidament, i a un interval menor de 1550ms i contesta perfectament. Quan tingui moltes dades i/o dispositius la comunicació serà mes lenta. Com sabem la longitud en bits del missatge a 9600 bps ….enviament+resposta… podem calcular el temps exacte. Altra cosa es que el node-red al ser un servidor web pugui controlar aquest temps tant acuradament.

Un dia podria fer un vídeo amb totes les possibilitats, hmi-waintek, kimco, classic-ladder, codesys, hmi-androids….

I amb tcp-ip que necessitaré altra llibreria arduino i la shield-ethernet, encara que en aquest cas ja millor una Raspberry, una vegada poses ethernet, es una opinió personal. Es complementen i aprens microcontroladors i microprocessadors.

  • tutorials/arduino/10_arduino_node-red.txt
  • Darrera modificació: 2020/09/08 19:36
  • per crevert