Develop your SCADA Application based on NodeRED

                                 

Course 8 Chapters of General Content [IS.AC002.GC]

#0 How to install Node-RED 

Node-RED is an open source programming tool, based in NodeJS, to develop applications in order to interact with different hardware's. Also Node-RED provides a browser editor that makes it easier to program and configure your own applications.

See more information of Node-RED in the official website:

  About Node-RED

The first step of this course will be how to install the Node-RED application in your personal computer. Before installing Node-RED, we must install NodeJS.  

How to install NodeJS

Just go to the official website of NodeJS and download the NodeJS source code or a pre-built installer for your OS:  Download NodeJS  

If you want to install it from the Linux command line:   

Add the repository:  
sudo apt install curl  

  • For the latest release, type:  

            curl -sL https://deb.nodesource.com/setup_10.x | sudo bash - 

  • For the LTS release, type:

            curl -sL https://deb.nodesource.com/setup_8.x | sudo bash - 

 Finally, to install NodeJS type: 

  sudo apt install nodejs  

How to install Node-RED

After installing the NodeJS, our computer is ready to install Node-RED.  The best way to install Node-RED is to use the node package manager, npm, which already comes with Node.js.  

  • WINDOWS OS

Type to the command prompt:

  npm install -g --unsafe-perm node-red

  • LINUX OS:

Type to command line:

sudo npm install -g --unsafe perm node-red

Take a look on the official installation guide to know more details:

Install Node-RED

Once you have installed Node-RED, you will be able to proceed with the next chapter of the course.

Odoo image and text block

Do you want to continue the course?...for free!!!

If you are interested in receive the next chapters for free just fill the form.

You will receive a weekly chapter to develop a NodeRed's Scada Application.

Industrial Applications can run under Open Source Platforms

Send

#1 MQTT client for Arduino based PLC as a I/Os module

In chapter #1 is explained how to configure your Industrial Arduino based PLC as an MQTT I/Os Module. 

Before starting this chapter would be interesting to take a look at how MQ TT protocol works, what is a JSON and their applications. 

After that, take a fast look at the next libraries:


The architecture of the code is made for an M-Duino 21+ but is designed to be extended for other M-Duino's. There are four different arrays that include all available I/Os in M-Duino 21+. 

These arrays are: digitalOutputs , analogOutputs, digitalInputs, analogInputs

So, every array will be structured as follows:

digitalOutputs[NUM_ZONES][NUM_DIGITAL_OUTPUTS_PER_ZONE] = {{Q0_0, Q0_1... }, {Q1_0, Q1_1...}, {Q2_0, Q2_1...} 

This last example will be for an M-Duino 58+, every zone is subdivided with their related pins nomenclature.

After explained how to access the mapping of the inputs we are ready to explain the rest of the code. 

In the  setup()  we just are making the initialization of the Ethernet and MQTT client called MQTT, relating the callback function when we receive a message through MQTT and all the actual input values. After that, we just have three main functions in the  loop()  function. We are calling the  reconnect()  function if there is no connection with the server, if there is a connection we are just using the  mqtt.loop() function that will maintain the connectivity with our server. Apart from these two functions, we have the  updateInputs()  that will update the real value of the inputs.


Let's go deeper into the functions to have a better understating of what they do:

  • OUTPUTS MANAGEMENT

First of all, we have the reconnect() function that will subscribe to the "Q" and "A" topics. One topic is dedicated to analog outputs and the other is dedicated to digital outputs. It is not connected to the server, then they will stop the client connection in order to not cause any issue with the server ports. 

Second, to understand how the outputs control works we need to look into the callback function. In this program the callback function is receiveMqttMessage() . The receiveMqttMessage() function will run every time that we receive a message. So, when we receive a message the callback function will decide if the topic is for analog or digital pins and then will call the right function for it, setDigitalOutput() function or setAnalgoOutput() function.

setDigitalOutput () extract and analyzes the JSON file, confirm that the zone and the pin exists and then execute the  digitalWrite()  function to update the desired output. 

The setAnalogOutput() function work exactly the in the same way that setDigitalOutput()

ANALOG_OUTPUTS_OFFSET is used to offset the value of the analog pins. Analog pins are placed in positions 5, 6,7. This is why the ANALOG_OUTPUTS_OFFSET is 5.

  • INPUT MANAGEMENT

In the main loop, we have the function updateInputs()that will update all the values of the inputs. This function will be comparing all the previous values to the current ones and if the new value has changed, then the program will publish the new value through MQTT. The digital values will be compering if there is a 0 or a 1, the function that makes this work is updateDigitalInput().

In the analog site the program will compare if the analog value has changed more than 5 points if the current value is higher or lower than the previous one, then will publish the new value. The function that makes this work is updateAnalogInput().


/*
   Copyright (c) 2019 Boot&Work Corp., S.L. All rights reserved
   This program is free software: you can redistribute it and/or modify
   it under the terms of the GNU Lesser General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.
   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU Lesser General Public License for more details.
   You should have received a copy of the GNU Lesser General Public License
   along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

// included library depending of M-Duino version 
#ifdef MDUINO_PLUS
#include <Ethernet2.h>
#else
#include <Ethernet.h>
#endif

// libraries needed for MQTT communication
#include <ArduinoJson.h>
#include <PubSubClient.h>
#define MQTT_ID "demo"
#define NUM_ZONES 1
#define NUM_DIGITAL_OUTPUTS_PER_ZONE 5
#define DIGITAL_OUTPUTS_OFFSET 0

const int digitalOutputs[NUM_ZONES][NUM_DIGITAL_OUTPUTS_PER_ZONE] = {
  {Q0_0, Q0_1, Q0_2, Q0_3, Q0_4},
};
#define NUM_ANALOG_OUTPUTS_PER_ZONE 3
#define ANALOG_OUTPUTS_OFFSET 5

const int analogOutputs[NUM_ZONES][NUM_DIGITAL_OUTPUTS_PER_ZONE] = {
  {A0_5, A0_6, A0_7},
};
#define NUM_DIGITAL_INPUTS_PER_ZONE 7
#define DIGITAL_INPUTS_OFFSET 0

const int digitalInputs[NUM_ZONES][NUM_DIGITAL_INPUTS_PER_ZONE] = {
  {I0_0, I0_1, I0_2, I0_3, I0_4, I0_5, I0_6},
};
#define NUM_ANALOG_INPUTS_PER_ZONE 6
#define ANALOG_INPUTS_OFFSET 7
#define ANALOG_INPUTS_THRESHOLD 5 // Filtering threshold

const int analogInputs[NUM_ZONES][NUM_ANALOG_INPUTS_PER_ZONE] = {
  {I0_7, I0_8, I0_9, I0_10, I0_11, I0_12},
};
byte mac[] = { 0xDE, 0xED, 0xBA, 0xFE, 0xFE, 0xAE };
IPAddress broker(10, 0, 3, 21);
unsigned port = 1883;
// Initialize client
EthernetClient client;
PubSubClient mqtt(client);
int digitalInputsValues[NUM_ZONES][NUM_DIGITAL_INPUTS_PER_ZONE];
int analogInputsValues[NUM_ZONES][NUM_ANALOG_INPUTS_PER_ZONE];

////////////////////////////////////////////////////////////////////////////////////////////////////
void setup(){ 
  Ethernet.begin(mac);
  mqtt.setServer(broker, port);
  mqtt.setCallback(receiveMqttMessage);
  // Init variables
  for (int i = 0; i < NUM_ZONES; ++i) {
    for (int j = 0; j < NUM_DIGITAL_INPUTS_PER_ZONE; ++j) {
      digitalInputsValues[i][j] = digitalRead(digitalInputs[i][j]);
    }
    for (int j = 0; j < NUM_ANALOG_INPUTS_PER_ZONE; ++j) {
      analogInputsValues[i][j] = analogRead(analogInputs[i][j]);
    }
  }
}

////////////////////////////////////////////////////////////////////////////////////////////////////
void loop() {
  if (!mqtt.connected()) {
    reconnect();
  } else {
    mqtt.loop();
  }
  updateInputs();
}

////////////////////////////////////////////////////////////////////////////////////////////////////
void updateInputs() {
  for (int i = 0; i < NUM_ZONES; ++i) {
    for (int j = 0; j < NUM_DIGITAL_INPUTS_PER_ZONE; ++j) {
      updateDigitalInput(i, j);
    }
    for (int j = 0; j < NUM_ANALOG_INPUTS_PER_ZONE; ++j) {
      updateAnalogInput(i, j);
    }
  }
}

////////////////////////////////////////////////////////////////////////////////////////////////////
void updateDigitalInput(int zone, int index) {
  int value = digitalRead(digitalInputs[zone][index]);
  if (value != digitalInputsValues[zone][index]) {
    digitalInputsValues[zone][index] = value;
    publishInput(zone, index + DIGITAL_INPUTS_OFFSET, value);
  }
}

////////////////////////////////////////////////////////////////////////////////////////////////////
void updateAnalogInput(int zone, int index) {
  int value = analogRead(analogInputs[zone][index]);
  int minValue = value > ANALOG_INPUTS_THRESHOLD ? value - ANALOG_INPUTS_THRESHOLD : 0;
  int maxValue = value < 1023 - ANALOG_INPUTS_THRESHOLD ? value + ANALOG_INPUTS_THRESHOLD : 1023;
  if ((analogInputsValues[zone][index] < minValue) || (analogInputsValues[zone][index] > maxValue)) {
    analogInputsValues[zone][index] = value;
    publishInput(zone, index + ANALOG_INPUTS_OFFSET, value);
  }
}

////////////////////////////////////////////////////////////////////////////////////////////////////
void setDigitalOutput(char *payload, unsigned int len) {
  DynamicJsonBuffer json(JSON_OBJECT_SIZE(3));
  JsonObject &root = json.parseObject(payload, len);
  if (root.success()) {
    int zone = root["zone"];
    if (zone >= NUM_ZONES) {
      // Invalid zone
      return;
    }
    int index = root["index"];
    index -= DIGITAL_OUTPUTS_OFFSET;
    if (index >= NUM_DIGITAL_OUTPUTS_PER_ZONE) {
      // Invalid digital output
      return;
    }
    int value = root["value"];
    digitalWrite(digitalOutputs[zone][index], value);
  }
}

////////////////////////////////////////////////////////////////////////////////////////////////////
void setAnalogOutput(char *payload, unsigned int len) {
  DynamicJsonBuffer json(JSON_OBJECT_SIZE(3));
  JsonObject &root = json.parseObject(payload, len);
  if (root.success()) {
    int zone = root["zone"];
    if (zone >= NUM_ZONES) {
      // Invalid zone
      return;
    }
    int index = root["index"];
    index -= ANALOG_OUTPUTS_OFFSET; 
    if (index >= NUM_ANALOG_OUTPUTS_PER_ZONE) {
      // Invalid analog output
      return;
    }
    int value = root["value"];
    analogWrite(analogOutputs[zone][index], value);
  }
}

////////////////////////////////////////////////////////////////////////////////////////////////////
void reconnect() {
  if (mqtt.connect(MQTT_ID)) {
    mqtt.subscribe("Q");
    mqtt.subscribe("A");
  } else {
    // MQTT connect fail
    client.stop();
  }
}

////////////////////////////////////////////////////////////////////////////////////////////////////
void receiveMqttMessage(char* topic, byte* payload, unsigned int len) {
  if (strcmp(topic, "Q") == 0) {
    // Set digital output
    setDigitalOutput((char*) payload, len);
  } else if (strcmp(topic, "A") == 0) {
    // Set analog output
    setAnalogOutput((char*) payload, len);
  }
}

////////////////////////////////////////////////////////////////////////////////////////////////////
void publishInput(int zone, int index, int value) {
  DynamicJsonBuffer json(JSON_OBJECT_SIZE(3));
  JsonObject &root = json.createObject();
  if (root.success()) {
    root["zone"] = zone;
    root["index"] = index;
    root["value"] = value;
    publish("I", root);
  }
}

////////////////////////////////////////////////////////////////////////////////////////////////////
void publish(const char *topic, JsonObject &root) {
  unsigned len = root.measureLength();
  if (len > 0) {
    char *payload = new char[len + 1];
    if (payload) {
      root.printTo(payload, len + 1);
      publish(topic, payload);
      delete[] payload;
    }
  }
}

////////////////////////////////////////////////////////////////////////////////////////////////////
void publish(const char *topic, const char *payload) {
  if (mqtt.connected()) {
    mqtt.publish(topic, payload);
  }
}

#2 How to visualize Inputs

In the previous chapters, we have seen how to install Node-RED on our computer and a useful Arduino code using Industrial Shields units. This code will be related during the entire course and we will be interacting with it to debug our own programs.

So, in chapter #2 we will step forward and we will see how to visualize the input values of our M-Duino into a Dashboard.

The first step is to install the stable version of Node-RED-Dashboards API. Remember that Node-RED-Dashboard requires Node-RED to be installed. Typing in the terminal we will go to our directory ~/.node-red using:

                            cd .node-red/
                          

Then we will type the command to install Node-RED-Dashboard:

                            npm install node-red-dashboard
                          

If you want more information about Node-RED-Dashboard take a look at the following link: Node-RED-Dashboard

Running Node-RED

Once Node-RED-Dashboard is installed we can proceed to start out the first program. 

First of all, we will learn how to execute the Node-RED and how to change our programs. If you have installed Node-RED you can use the node-red command:

                            node-red
                          
Running Node-RED - Chapter 2 - Develop you SCADA Application based on Node-RED

After running the node-red command we are able to go to the localhost:1880/

Type localhost:1880/ in your browser. You will see the Node-RED user interface where we will be able to develop, debug and modify our programs.

Node-RED user interface - Chapter 2 - Develop you SCADA Application based on Node-RED

Now we are able to see the different Node-RED blocks on the right side. If you click on a module you will see that on the left side is showed some help documentation that will be very useful to understand the block. 

Creating our first program

Finally, our first step creating our program:

Take one MQTT input blog.

This module will be our MQTT input message from the MQTT broker. Make a double click and change the topic for "I". This change will relate the input blog with all the messages that we receive through the topic "I". Actually, the block has subscribed to "I". 

Moreover, we must change the server. You will see that you don't have any server created. Create a new server, name it and configure it as follows. These will be the IP address and the port where the MQTT broker is placed. Take a look at the next picture. We have named the server "Localhost broker".

Connection - Chapter 2 - Develop you SCADA Application based on Node-RED

Our MQTT subscription is set properly.

From this block we will receive a JSON string with the following structure:

                                    "{"zone" = 0, "index" = 0 , "value" = 0 }"
                                  

Work with the string it is not practical. For this reason, we have to convert this string to a JavaScript object. Node-RED has one blog dedicated to doing this job. This block is named Json and we can find it in the functions blocks. Double click on this block. Change the action to "Always convert to JavaScript Object". Now to the output of this message we will have the JavaScript object and will be very easy to organized our program.

Always convert to JavaScript Object - Chapter 2 - Develop your SCADA application based on Node-RED

After converting the message from the MQTT "I" topic to JavaScript we are ready to analyze the object and divided into different zones and indexes. In this course, we just will see an example of M-Duino 21+. So we will have the following input structure.

ZONE 0

  • INDEX 0 = I0_0

  • INDEX 1 = I0_1

  • INDEX 2 = I0_2

  • INDEX 3 = I0_3

  • INDEX 4 = I0_4

  • INDEX 5 = I0_5

  • INDEX 6 = I0_6

  • INDEX 7 = I0_7

  • INDEX 8 = I0_8

  • INDEX 9 = I0_9

  • INDEX 10 = I0_10

  • INDEX 11 = I0_11

  • INDEX 12 = I0_12

ZONE 1

(M-Duino 42+, M-Duino 38R, M-Duino 38AR+...):

ZONE 2

(M-Duino 57R+, M-Duino 58+...)

Take a look in the M-Duino 21+ inputs, and you will see that from I0_7 to I0_12 are Analog inputs . In fact, the values will be represented in a different way because one is digital and the other will be Analog (from 0 to 1024). But we will see it later. 

M-Duino 21+ Inputs - Chapter 2 - Develop you SCADA Application based on Node-RED

The first split to the object will be the zone.

Node-RED has a block named switch that will be very useful to divide the information and direct it to where we want. So, to filter the zone we will add the switch and with a double click we will change the configuration. Add two new conditions and configure them in the following picture. Adding payload.zone we will indicate with part of the object the switch will have to look on in order to classify the message. 

Zone switch - Edit switch node - Chapter 2 - Develop you SCADA Application based on Node-RED

After the first zone switch, we will proceed to do the same for the index. 

Index switch - Edit switch node - Chapter 2 - Develop you SCADA Application based on Node-RED

Before placing the final blocks so as to display the values to the dashboard we need to create some groups. 

On the right side, we have three different tabs. One to see the debug messages, another for information and another one for the distribution of our dashboard.

On the Layout side, we have distributed the space in three groups, digital inputs and two for Analog inputs. When we create the proper dashboard blocks we will decide where to place these blocks on our dashboard. Apart from that, there are other configurations inside and theme tabs that will give a customized look to our dashboard. 

So, after having analyzed and divided the full message, now we just need to show in our dashboard the real and updated value. In fact, we just need to connect the switch block for digital inputs and the gauge block for the Analog inputs, you may find these blocks in the dashboard group on the left side. 

Node-RED also has the options like a chart for Analog signals. 

Take a look on the following pictures to have a better understanding.

Inputs Dashboard - Chapter 2 - Develop your SCADA application based on Node-RED

Digital Inputs - Edit switch node - Chapter 2 - Develop your SCADA application based on Node-RED







Analog inputs - Edit gauge node - Chapter 2 - Develop your SCADA application based on Node-RED






You will have realized that in the last block picture we have placed the msg block. This blog is the debug block. The debug block is very useful to debug our program and follow up every function in order to see how the message flows in our workflow. 

Finally, we have our logic programmed and we just need to customize our dashboard. Configure the Layout, Site and Theme to give your desired look to your dashboard. As an example take a look at the following pictures.

Dashboard Layout - Chapter 2 - Develop your SCADA application based on Node-RED


Dashboard Site - Chapter 2 - Develop your SCADA application based on Node-RED


Dashboard Theme - Chapter 2 - Develop your SCADA application based on Node-RED

Finally, the work is done. With the purpose to test our new program, we will have to the mosquito MQTT broker in our computer and connect the M-Duino to the same  network. Next is showed a picture of how the dashboard will be displayed.

Node-RED Dasboard - Chapter 2 - Develop your SCADA application based on Node-RED

Besides, you have a video example of how you will see the dashboard in real-time while the input values are changing. 


 
 

#3 How to interact with Outputs

In chapter #3 is showed how to interact with our outputs of an Industrial Shields PLC.  Before starting, this chapter makes sure that you have achieved the steps of the previous chapters. During the course, we will be increasing the Node-RED application, so in this one, we already have the inputs in the dashboard.

Requirements

  • Node-RED and NodeJS installed and running

  • Node-RED-Dashboard installed

  • Mosquito MQTT broker installed and running

  • PLC with the chapter #1 code running

Once the requirements are achieved, we will start explaining how the architecture of the output will work.

Outputs Architecture

As commented before, this course is designed to be used with an M-Duino PLC family. As an example, we use an M-Duino 21+.

In our M-Duino 21+ we have 8 digital outputs (from Q0_0 to Q0_7), apart from that we know that three of these digital outputs can work as analog outputs. So in our example will have 5 digital outputs (from Q0_0 to Q0_4) and 3 analog outputs (from A0_5 to A0_7). To extend the Node-RED App to more outputs, the procedure is exactly the same.

Adding a new tab

To have a better organization of our dashboard will be properly placing a new tab where we can control our outputs and not mix them with our inputs. Obviously, this is an example, in your application you can place the different components regarding your needs. 

So, in the first part of this chapter, we will see how to add a new tab. 

Go to the Node-RED user interface through your browser. At the top right, select the menu and click on flow --> Add a new flow. Then go to renamed a called Outputs flow. 

Edit flow 4 - Chapter 3 - Develop your SCADA application based on Node-RED

After that, an automatic tab will be created. On our Layout, we will be able to named and add the new groups. Name your new tab Outputs and add two new groups one called Analog Outputs and the other called Digital Outputs. 

Dashboard Layout- Chapter 3 - Develop your SCADA application based on Node-RED

 

Now we are ready to add the dashboard items. Add a Switch and a Slider. Click on them and name them. Select also the group to be placed select Analog outputs for the slider and Digital Outputs for the switch.

*Do not forget to add the topic at the bottom of the Edit switch node. A for Analog Outputs and Q for Digital outputs.

Topics of Edit Switch - Chapter 3 - Develop your SCADA application based on Node-RED
Digital Inputs - Edit Switch node - Chapter 3 - Develop your SCADA application based on Node-RED
Analog Inputs - Edit Slider node - Chapter 3 - Develop your SCADA application based on Node-RED

Sending the MQTT Message

Since here, we have placed the items on our dashboard. So, now it's time to proceed to connect these items to the MQTT broker, in other words, send a message with the topic A and Q.

First, it's required to relate the switch and slider to the right JS object. Select the Change function and configure it as follows:

Edit Change node 1 - Chapter 3 - Develop your SCADA application based on Node-RED

*For every different input, we must set the index value. In this case, is a "0" because we are configuring the Q0_0.

The change function will add new content, now we have an index apart of the payload where we have the value of the switch or the analog slider. Besides the index, we need to add the zone. So, add another change function and add the zone parameter. Take a look at the next picture: 

Edit Change node 2 - Chapter 3 - Develop your SCADA application based on Node-RED

*For every different zone, we must set the proper value of the zone. In this case, is a "0" because we are configuring the zone 0 (between Q0_0 and A0_7).

Now we have the JS object set it properly. Now we just must need to create the JSON and send it to the MQTT broker. Take the json function and edited as "Always convert to JSON String". Then add the MQTT output function and select the broker. In our case, our broker is running on our computer so we select "Localhost broker". 

*Remember that we have added the topic into the switch function. We can also define the MQTT output function, but then we will have to create two different blocks. 

Take a look at the example of how to set the json function and the MQTT output function. 

Edit json node - Chapter 3 - Develop your SCADA application based on Node-RED
Edit mqtt out node - Chapter 3 - Develop your SCADA application based on Node-RED

Finally, add the entire I/Os that you want to use and place the switch and the sliders where you want.

Our Node-RED program should look like

Node-RED program - Chapter 3 - Develop your SCADA application based on Node-RED
Outputs - Node-RED program - Chapter 3 - Develop your SCADA application based on Node-RED

#4 How to configure your Industrial Shields PLC through MQTT

In chapter #4 is explained how to configure our PLC. Industrial Shields PLCs have an EEPROM where you can set and save some important values that are very relevant for your application. 

In this chapter, we will follow an imaginary application. Imagine an;  HVAC (Heating, Ventilating and Air Conditioning) an application where you have a value of humidity and temperature that are your reference point for the system. These values shall be in your EEPROM to not lose it during a power cut and when the PLC restart again just take the last value of the EEPROM to continue with their functionality.  

So, in the post, we will learn how to use our EEPROM, how to send from the Node-RED values well interpreted by the PLC and how to get values properly interpreted from the  PLC to the Node-RED application.

Requirements

The requirements for this chapter are:

  • Mosquitto or other MQTT broker running

  • Node-RED installed and running

  • M-Duino family PLC

  • Ethernet Network

  • Arduino IDE installed with the proper MQTT library

*Follow the previous chapters to see these steps.

Let's begin with the practical part. In the beginning, we will start with the Node-RED application. Our Node-RED app will consist of an Edit Numeric Node where the user will be able to introduce the desired value of our system. So, add two Edit Numeric Node from the dashboard functions. 

Our temperature range will be between 15-35ºC (step 0.1) and our humidity will be between 0-100% (step 1). 

Edit numeric node 1 - Chapter 4 - Develop your SCADA Application based on Node-RED


Once the two Edit Numeric Nodes are placed just configure them to show in the third tab of our dashboard as a configuration tab. 


Configuration Tab - Chapter 4 - Develop your SCADA Application based on Node-RED

Now our dashboard looks good. Just let's attend to the logic of the application. We want to send this value directly to our MQTT broker. Before doing that we will have to make some changes to this variable to make it easier to understand the data in our controller. All Java Scrips variables are 64 bits, this is a problem for us because Arduino Mega or   Leonardo used in Industrial Shields PLC just use as a maximum accepted of 32 bits. 

In order to change this value, we will have to create a new function in our Node-RED workflow.

Add an empty function and configured as follows:

Edit function node 1 - Chapter 4 - Develop your SCADA Application on Node-RED

Buffer.allocUnsafe() function returns a new uninitialized buffer of the specified size. In our case we want a 4 bytes buffer, take a look at this link to know more about the NodeJS libraries  NodeJS buffer information

Once the buffer is initialized we can proceed to transform out value to a int32_t little endian as our controller can read with any problem. To do that we are using the writeInt32LE(value, offset) function. Read more about it in  Buffer WriteInt32 function

To give more resolution to our system we have decided to multiply the temperature value by 10 in order to treat with centenary numbers in our PLC or controller. So add a short function like that:

Edit function node 2 - Chapter 4 - Develop your SCADA Application on Node-RED

After that our value is ready to be sent. Add the MQTT send function with the desired topic. At the moment our workflow shall look like this:

Workflow shall 1 - Chapter 4 - Develop your SCADA Application on Node-RED

Edit mqtt out node - Chapter 4 - Develop your SCADA Application on Node-RED

Half of the Node-Red is done. As you already know NodeRED nodes actuate when the node suffers a change, then imagine that for some reason you have a power cut in your system, the PLC will be working as expected because it has the configured value in their EEPROM, but the system will not know in which value the PLC is working. To solve this issue we will send through the same topic the configured value saved into the PLC EEPROM when the PLC restarts its functionality. 

How we relate this to our Edit Numeric Node. It just the same as before but in the other way.

Add a receive MQTT function, subscribe to the same topic and then add an additional editable function. Configure the function as follows: 

Edit function node 3 - Chapter 4 - Develop your SCADA Application on Node-RED

As you can see we just use one function here. Take a look at the previous NodeJS links to look how this function works. Basically, the function will read a value of 32 bits in little endian and change it for a Java Script value. The value is properly treated for our Node-RED application. Just add a new function to divide the temperature value by 10. 

Our workflow shall look like this:

Edit function node 4 - Chapter 4 - Develop your SCADA Application on Node-RED
Workflow shall 2 - How to configure your Industrial Shields PLC through MQTT

PLC software

Following the Node-RED application, we will need a controller that can interact with it. In this second part of the chapter, it's showed an example code. The code is a simple example of how to use our EEPROM, structures and MQTT communication protocol.

In our Arduino based PLC, we will have a structure made of two int32_t, one for temperature and another for humidity. These values will be system values to set the behavior of the system.

//Configuration structure
typedef struct {
  int32_t temperature;
  int32_t humidity; 
} actualConfig_t; 

actualConfig_t actualConfig;

In order to get and put values to the EEPROM, we will use the EEPROM.h library. The code uses two main functions of this library, one is EEPROM.get(eaddress, variable); to get values and EEPROM.put(address, variable; to safe values.


The other part of the code that is very important is how to read the values from the MQTT. To do that we use the setCallBack function. 

mqtt.setCallback(receiveMqttMsg);
This function will be called every time than we receive an MQTT message.
 
void receiveMqttMsg(char* topic, uint8_t* payload, unsigned int len){
    if (strcmp(topic, "configTemp") == 0){
        //Set config to variable 
        memcpy(&actualConfig.temperature, payload, len);
        //Print to debug
        Serial.print("Temperature set to: ");
        Serial.println(actualConfig.temperature);
        //Set config to EEPROM
        EEPROM.put(eeAdressTEMP, actualConfig.temperature);
    }
    if (strcmp(topic, "configHum") == 0){
        //Set config to EEPROM
        memcpy(&actualConfig.humidity, payload, len);
        //Print to debug
        Serial.print("Humidity set to: ");
        Serial.println(actualConfig.humidity);
        //Set config to EPROM
        EEPROM.put(eeAdressHUM, actualConfig.humidity);
    }
}

This function will compare the topic to know if the value is using temperature or humidity:  strcmp(topic, "configTemp") == 0
Then will use the memcpy(&adress, data, datalen) to store the data from the MQTT callBack function to our working variable. If in the NodeRED application we didn't modify the data, this process will be much more complicated. Now we have a little endian int32_t data that is very easy to treat. 

Once the data is in our RAM is very easy to store information in the EEPROM just using the EEPROM.put() function.

Apart from on the code, it is created a debug function that you can use to know the state of your variables during the workflow. 

Below is shown the complete code:

////////////////////////////////////////////////////////////////////////////////////////////////////
//Configuration structure
typedef struct {
  int32_t temperature;
  int32_t humidity; 
} actualConfig_t; 

#define MQTT_ID "demo"

//including library for MQTT
#include <PubSubClient.h>

//including EEPROM library
#include <EEPROM.h>
//including Ethernet library depending of M-Duino version 
#ifdef MDUINO_PLUS
#include <Ethernet2.h>
#else
#include <Ethernet.h>
#endif

actualConfig_t actualConfig;
const byte eeAdressTEMP = 0;
const byte eeAdressHUM = 4;
byte mac[] = { 0xDE, 0xED, 0xBA, 0xFE, 0xFE, 0xAE };
IPAddress ip(10, 10, 11, 2);
IPAddress broker(10, 10, 11, 1);
unsigned port = 1883;
EthernetClient ethClient;
PubSubClient mqtt(ethClient);

void setup(){
    Serial.begin(9600); 
    //Set up Ethernet
    Ethernet.begin(mac, ip);
    Serial.print("Local IP: ");
    Serial.println(Ethernet.localIP());
    //Set up MQTT 
    mqtt.setServer(broker, port);
    mqtt.setCallback(receiveMqttMsg);
    updateFromEEPROM();
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void loop(){
    if (!mqtt.connected()){
        reconnect();
        if (mqtt.connected()){
            mqtt.publish("actualConfigTemp", (uint8_t*) &actualConfig.temperature);
            mqtt.publish("actualConfigHum", (uint8_t*) &actualConfig.humidity);
        }
    }else{
        mqtt.loop();
    }
//Rest of the logic
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void reconnect(){
    if (mqtt.connect(MQTT_ID)){
        mqtt.subscribe("configTemp");
        mqtt.subscribe("configHum");
    }else{
        //connection fail
        ethClient.stop();
    }
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void receiveMqttMsg(char* topic, uint8_t* payload, unsigned int len){
    if (strcmp(topic, "configTemp") == 0){
        //Set config to variable 
        memcpy(&actualConfig.temperature, payload, len);
        //Print to debug
        Serial.print("Temperature set to: ");
        Serial.println(actualConfig.temperature);
        //Set config to EEPROM
        EEPROM.put(eeAdressTEMP, actualConfig.temperature);
    }
    if (strcmp(topic, "configHum") == 0){
        //Set config to EEPROM
        memcpy(&actualConfig.humidity, payload, len);
        //Print to debug
        Serial.print("Humidity set to: ");
        Serial.println(actualConfig.humidity);
        //Set config to EPROM
        EEPROM.put(eeAdressHUM, actualConfig.humidity);
    }
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void updateFromEEPROM(){
    EEPROM.get(eeAdressTEMP, actualConfig.temperature);
    EEPROM.get(eeAdressHUM, actualConfig.humidity);
}
void debug(){
    Serial.println("------------------Debug:-------------------");
    Serial.print("Temp Value:");
    Serial.println(actualConfig.temperature);
    Serial.print("Hum Value:");
    Serial.println(actualConfig.humidity);
}


Below is attached a video where it is shown the functionality of the system. On one side we have the NodeRED dashboard and on the other side, we have the serial monitor of Arduino IDE to see how we receive the data. Remember that NodeRED and Mosquitto are running and M-Duino PLC is connected to the same network of the MQTT broker and NodeRED service: 

 
 

#5 Communications: How to read variables from a Slave device      through Modbus TCP/I

Introduction

The content of chapter #5 is focused on controlling a slave device through Modbus TCP/IP. This device is connected to the same network through Ethernet. In this case, the device is an M-Duino 21+ using the slave code that is posted in our blog.

This is the first requirement to complete and try the new  lesson,  apart from having the Node-RED installed. 

Modbus is a very useful standard that allows us to communicate several devices from different manufacturers in the same network. Besides Modbus TCP/IP is transferred through Ethernet which is one of the most reliable protocols of the market.

In this case, we will show how to configure Node-RED in order to control one of the slaves of our system. 

Modbus masters normally have 8 main functions:

readCoils();
readDiscreteInputs();
readHoldingRegisters();
readInputRegisters();
writeSingleCoil();
writeSingleRegister();
writeMultipleCoils();
writeMultipleRegisters();


Installation

So, first of all, we will need to install the proper library in order to have these functions available in our Node-RED application.

Type in your terminal or command prompt after placing your terminal in the root directory of your Node-RED, normally use the .node-red/ :

 cd .node-red/npm install node-red-contrib-modbustcp

Now you will see in your Node-RED application that you have two new Modbus functions available.

Developing the Application

Often in our installations, we have the same two main functionalities to control. Inputs and Outputs. As we have done in the previous chapters we have split the content. 

Inputs

/////////////////Developing////

Outputs

  Modbus TCP client node. Connects to a Modbus TCP server to write msg.payload to a coil or register.

Function codes currently supported include:

  • FC 5: Write Single Coil

  • FC 6: Write Single Holding Register

  • FC 15: Write Multiple Coils

  • FC 16: Write Multiple Holding Registers

By choosing the dataType and the Address we will be able to access our slave outputs and control them.  

After installing the Node-RED library to use the Modbus TCP/IP communication protocol, we are able to use their nodes. But before using the main block that will arrange the communication, we will have to configure some parameters. We will have to specify the Type and the Address. The type will be the Modbus function and the Address will be the position of the Node-RED array where are our desires I/Os placed.

Select one switch and name as you wish. There is no additional configuration for this node. 

Then select the change block placed on the functions blocks. Type address on the function to give an address to the Java Script Object. 

Java Script Object - Edit change node - Chapter 5 - Develop your SCADA Application on Node-RED

After that, our switch will send a message (1/0) and additionally will have an address that is pointing to the right output.

Do the same for the dataType, add a change function type dataType and give the same for all the switches, FC5.

Switches - Edit change node - Chapter 5 - Develop your SCADA Application on Node-RED

Finally, select the modbustcp block into the outputs nodes. Give a name to the block. Into the configurations, Type and Address are toked for the node if you are not defined before for other nodes. In our case, we will define before having just one connection opened with our PLC. 

So, select any Type and Address. Then configure your Modbus IP Server to the node, in our example, we have used 10.10.10.4 IP. 

Modbus IP Server to node - Edit change node - Chapter 5 - Develop your SCADA Application on Node-RED

If we do the same process for Analog outputs, adding sliders instead of switches, we got the entire control of the outputs. For the second change node, we must select dataType as FC6. 

Below is shown the entire output flow and the user interface. 

Output flow and user interface - Chapter 5 - Develop your SCADA Application on Node-RED
Node-RED Dashboard - Chapter 5 - Develop your SCADA Application on Node-RED

#6 Install NodeRed on Raspberry Pi or Industrial Panel PC based   on Linux

Introduction

The content of chapter #6 is how to install our Node-RED application to one of our Touchberry Pi or other Panels PC based in Linux. This type of application are very used for our customers because it's an easy and robust way to have an SCADA touch panel next to the installation. Another point that we will focus on during the chapter is how to add the autostart capability, so once we were torn on the panel the system will show us directly the Node-RED application. 

Installation and Upgrade

Depending on which version of Panel PC, we will have installed different versions of Debian or Ubuntu. Upgrade or install the last version of Node-RED into your system by typing in the terminal:  

                            b
                            
                              ash <(curl -sL https://raw.githubusercontent.com/node-red/raspbian-deb-package/master/resources/update-nodejs-and-nodered)       
                            
                          

Update npm before installing any package using the following commands:

                             
                            
                              cd ~/.node-red npm outdated npm update
                            
                             
                          

Running Node-RED

To start Node-RED open a new terminal window and type:

                        node-red-start
                      

*Closing the terminal or ctrl-c does not stop the Node-RED from running. It will continue running in the background. 

To stop, type:

                        node-red-stop
                      


Autostart on boot

Probably, in your application, you will want that the Node-RED start when you turn on your Touchberry Pi. In order to do that, we will open the terminal again and type:

                        sudo systemctl enable nodered.service
                      

and to disable type:

                        sudo systemctl disable nodered.service
                      


These are the basics to install Node-RED to our Touchberry Pi or other Linux based Industrial Shields panel PC. 

If you need additional information about that follow the next  link.

#7 Alarms Manager by Email

The content of chapter #7 is focused on generate alarms with Node-RED. Basically, we will see how to create a module that will send an email if some parameter exceeds the desired value.

The prerequisite of this chapter is to have an email server with valid credentials.

Installation

Run the following command in your Node-RED user directory - normally as we already know ~/.node-red  :

                            cd .node-red
sudo npm i node-red-node-email                    
                          
Command NodeRED user directory - Alarms Manager by Email

Once the blog is installed, take a look at the social blocks on the Node-RED application. You will see an output block called email. Take it to your flow, also add an inject block. 

In our inject block, we will configure with a topic and a payload. The topic will be our Email subject and the payload will be the content of our Email. Next is showed an example:

Node properties - Chapter 7 - Develop your SCADA Application based on NodeRED

After that, we just have to connect it to our email output block and configure it. You must have a valid email and the security well configured. Configure your block as follows, remember that To is the email ID where you want to send the email and User ID and Password are for the email from where you want to send the email:

Automatic Email inject node 2 - Alarms Manager by Email

When you will click the blog, the email must be sent to the destination. If you want to automate that, you just need to create a function that controls the flush of the email data.

If you see the following error:

Error Invalid Login - Alarms Manager by Email

Configure your email to accept  Control access to less secure apps  these must unblock Node-RED to use your email. In this example, we have used Gmail. To change these parameters, you just need to go to your secure configuration on your Gmail account. With other email service providers, contact their technical team to know how to unblock the control access.


#8 Using SQLite DataBase with NodeRED

Introduction

SQLite is a language Library C based that implements SQL database engine. It’s small, fast, self-contained, high-reliability and full-featured. Apart from that is based on open source and it’s the most used database in the world. 

In this chapter, we will focus our efforts to explain how to implement a database using SQLite with Node-RED.

SQLite - Chapter 8 - Develop your SCADA Application based on Node-RED

Installation

Type in your terminal:

                        
sudo npm install -g [email protected]
hash -r
cd ~/.node-red
npm install node-red-node-sqlite

Once, we have the library installed in our Node-RED we are able to use their blocks on it. Another useful tool that we recommend using, DB Browser for SQLite. There are distributions for the most popular OS.


Example

Before starting with the example, it's important to check how SQLite works. If it's your first time using SQLite take a look at their website
Before introducing data to the database we must create it. To create a database we just have to execute this code:  

                          
CREATE TABLE IF NOT EXISTS log(  time INT PRIMARY KEY, value INT) 
In order to execute this code, we must add the main SQLite block to our workflow. We will have to edit the block and create the database. Clicking, inside the block, on the edit button next to the database name we will create a new file where our database will be placed. In our case, we have created the Chapter8_SQLite.db file. 
Also, by adding a timestamp and editing the Topic with the previous code we will create a database inside the file. 
Take a look at the next pictures to have a better understanding of this procedure: 

Chapter8_SQLite.db file - Chapter 8 - Develop your SCADA Application based on Node-RED
Edit SQLite node - Chapter8_SQLite.db file - Chapter 8 - Develop your SCADA Application based on Node-RED
Edit inject node - Chapter8_SQLite.db file - Chapter 8 - Develop your SCADA Application based on Node-RED

Now we can click on deploy and after that clicking on the Create DB Table, we will execute the SQLite code through the block and our database will be created properly.

Once we have created the database, it's time to introduce our first characters and read them. Imagine that we have a data logger PLC that is sending to our Node-RED application the analog value of some sensor, and our project is to know their values over time. Then we have to create a database where the time and the values must be at the same row of our database. 

In order to do that, we have created a user interface as an example with a slider, a button and a text. The slider will be to simulate the external analog value, the text will be used to show the last value in our database and the button will be to update the last value that will be displayed in the text. 

How to do that? It's quite simple.

First of all, introduce the slider and configure them as you wish. It's important to check the case output "only on release", if not on every change we will save a value on our database. After configuring our slider we must introduce a function between the slider and the SQLite block. 

On this function, we will place the next code following the SQLite bases:

                            
return {
"topic": "INSERT INTO log (time, value) VALUES ( " + Date.now() +", "+ msg.payload +")"

This piece of code will be launched every time that we change the value on the slider to the SQLite block. With this, we will insert a value and a timestamp into our table. 

Next are shown some pictures of this process:

SQLite bases - Chapter 8 - Develop your SCADA Application based on Node-RED
 
Edit slider node - SQLite bases - Using SQLite DataBase with NodeRED
 
Edit function node - SQLite bases - Chapter 8 - Develop your SCADA Application based on Node-RED

The last step will be displaying the last value of our database. We use the same method, just adding a different code on our function block:

var time = msg.payload
var newMsg = {
"topic": "SELECT * FROM log ORDER BY time DESC LIMIT 1"

return newMsg;

Next is shown the configuration of every block in case that there is any doubt:

Configuration of every block - Using SQLite DataBase with NodeRED
 
Edit function node - Configuration of every block - Chapter 8 - Develop your SCADA Application based on Node-RED
 
Edit text node - Chapter 8 - Develop your SCADA Application based on Node-RED

Finally, add your customized parameters to the layout. 

Hope that this chapter has helped you to have a new feature of Node-RED and that you now are able to implement databases in your project. 

Next is showed our example layout: 

Example layout - Chapter 8 - Develop your SCADA Application based on Node-RED