Develop your SCADA Application based on NodeRED
Course 10 Chapters of General Content [IS.AC002.GC]
HOW TO INSTALL NODE-RED
NodeRED is a open source programming tool based in NodeJS, to develop applications in order to interact with different hardware. Node-RED also provides a browser editor that makes easier to program and configure your own applications.
To see more information of Node-RED in the official website: Node-RED about
The first step of this course will be installing the NodeRed application in your personal computer, but before installing Node-RED, you must install NodeJS.
HOW TO INSTALL NODEJS:
Go the official website of NodeJS and download the NodeJS source code or a pre-build installer for your OS: NodeJS Download
If you want to install it from the Linux command line, follow the next steps:
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 in your computer, it is ready to install Node-RED. The best way to install Node-RED is to use the node package manager npm that 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 at the official installation guide to know more details: Node-RED install
Once installed, open a command prompt and run the following command to ensure Node.js and npm are installed correctly.
WINDOWS OS:
Using Powershell:
npm --version
LINUX OS:
Using cmd:
node --version && npm --version
Once you have installed Node-RED, you can continue with the
next chapter
of the course, but if you have any doubt, check this link.
The next thing is about installing Node Red on your Raspberry Pi.
Install Node Red on your Raspberry Pi
The content of this chapter is how to install our NodeRED application to one of our Touchberry Pi or other Panels PC based in Linux. This type of applications are very used for our customers because it's a easy and robust way to have a SCADA touch panel next to the installation. Another point that we will focus during the chapter is how to add the autostart capability, so once we torn on the panel the system will show us directly the NodeRED application.
Installation and Upgrade
Depending which version of Panel PC we will have installed different version of Debian or Ubuntu. Upgrade or install the last version of NodeRED into your system typing in the terminal:
bash <(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 comands:
cd ~/.node-red npm outdated npm update
Running NodeRED
To start NodeRED open a new terminal window and type:
node-red-start
*Closing the terminal or ctrl-c does not stop the NodeRED 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 NodeRED 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 NodeRED to our Touchberry Pi or other Linux based Industrial Shields panel PC.
If you need additional information about that follow the next link.

Do you want to continue the course?...for free!!!
If you are interested in
You will receive a weekly chapter to develop a NodeRed's Scada Application.
Industrial Applications can run under Open Source Platforms
MQTT client for Arduino based PLC as a I/Os module.
In this chapter #1 it is explained how to configure your Industrial Arduino based PLC as a MQTT I/Os Module.
Before starting this chapter, it would be interesting to take a look
After that, take a fast look on the next libraries:
-
MQTT: MQTT Post
-
ArduinoJson: ArduinoJson library
(The version of the Json library has to be the ArduinoJson-5.13.5.zip one).
The architecture of the code is written for
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 would be for a M-Duino 58+. As you can see, every zone is subdivided with their related pins nomenclature.
After explaining how to access to the inputs mapping you are ready to learn the rest of the code.
In the setup() we are just making the initialization of Ethernet and the MQTT client called mqtt, relating the callback function when we receive a message through MQTT and allthe actual input values. After this, we just have three main functions in the loop() function. The first one is the reconnect() function and we'll call it if there is no connection with the server. If there is connection we are going to use the mqtt.loop() function that will maintain the connectivity with our server. Apart of this two functions we also have the updateInputs() funcion that will update the real value of the inputs at every loop.
Let's go deeper in the functions to have a better understating of what they do:
-
OUTPUTS MANAGEMENT:
Firs of all, we have the reconnect() function that will subscribe to the "Q" and "A" topics. One topic is dedicated to analog outputs ("A") and the other one ("Q") is dedicated to the digital outputs. If the M-Duino PLC is not connected to the server, then they will stop the client connection in order not to cause any issues with the server ports.
The second step is to understand how the outputs' control works. In order to know this, you'll need to look into the callback function. In this program the callback function is receiveMqttMessage(). This function will run every time that we receive a message and it decides if the topic is for the analog or the digital pins. After that, the right function for it is called;
The setAnalogOutput() function works exactly the same way.
The ANALOG_OUTPUTS_OFFSET constant is used to offset the value of the analog pins. Analog pins are placed in the 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 values have changed, then the program will publish the new values though MQTT. The digital values will be comparing if there is a 0 or a 1, the function that make this work is updateDigitalInput().
In the analog site the program will compare if the analog value
/*
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}, //adapt it to the M-Duino model that you are using
};
#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}, //adapt it to the M-Duino model that you are using
};
#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}, //adapt it to the M-Duino model that you are using
};
#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}, //adapt it to the M-Duino model that you are using
};
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);
}
}
How to visualize inputs
In the previous chapters we have seen how to install Node-RED in our computer and a useful Arduino code for Industrial Shields PLCs .
In this
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.
Go to the terminal and go to the directory where Node-RED was installed in:
cd .node-red
The next step is typing the command to install the Node-RED-Dashboard:
npm node-red-dashboard
If you want more information about the Node-RED-Dashboard take a look in the following link: Node-RED-Dashboard
RUNNING NODE-RED
Once Node-RED-Dashboard is installed we can proceed to start out firs program.
First of all, we will learn how to execute and work with Node-RED. If you have installed Node-RED you can use the following command:
node-red

After running the node-red command we are able to go to the mqtt://localhost:1880/ in your Raspberri PI device or to http://127.0.0.1:1880/ in your computer.
You will see the Node-RED user interface where we will be able to develop, debug and modify our programs. It will look like the following image:

Now we are able to see the different Node-RED blocks at the right site. If you click on a module you will se that in the left site is showed some help documention that will be very useful to understand the block.
CREATING OUR FIRST PROGRAM
Take one MQTT input blog.
This module will be our MQTT input message from the MQTT broker. Make double click and change the topic for "I". This change will relate the input blog with all the messages that we receive through topic "I". Actually the block have subscribed to "I".
Moreover we must change the server. You will see that you don't have any server created. Create a new server, named and configured as follows. These will be the IP address and the port where the MQTT broker is placed. Take look on the next picture. We have named the server "Localhost broker".

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 string it's not practical. For this reason we have to convert this string to a JavaScript object. Node-RED has one blog dedicated to do this job. This block is named json and we can find it into the functions blocks. Double click to 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 east to organised our program.

After converting the message from the MQTT "I" topic to a JavaScript we are ready to analyze the object and divided to the 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 representative in a different way, becouse one is digital and the other will be analog (from 0 to 1024). But we will see it later.

The first split to the object will be the zone.
Node-RED have a block named switch that will be very useful to divide the information an direction it to where we want. So, to filter the zone we will add the switch and with double click we will change the configuration. Add two new conditions. And configured as 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.

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

Before placing the final blocks so as to display the values to the dashboard we need to create some groups.
At the right site we have the three different tabs. One to see the debug messages, another for information and another one for the distribution of our dashboard.
In the Layout site 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 block on our dashboard. Apart of that there is other configurations in site and theme tabs that will give a customized look to our dashboard.
So, after 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 this blocks in the dashboard group on the left site.
Node-RED also have the options like a chart for analog signals.
Take a look on the following pictures to have a better understanding.

Make sure that all the blocks by the name "set msg.payload" are set like this, so the message can arrive to it's destination.
Make sure that all the blocks by the name "switch" and "gauge" have the right configuration shown bellow:
Once everything is configured, your screen is going to look like this one: (this is the configuration of a M-Duino42+).
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 dash board. As example take a look on the following pictures.



Finally the work is done. With the porpoise to test our new program we will have to download and open in the cmd window the mosquitto MQTT broker in our computer and connect the M-Duino to the same network.
-
Download the zip file on this link: http://www.steves-internet-guide.com/install-mosquitto-broker/
-
Open a new cmd window and go to the location where the file went (it usually goes to "Downloads").
-
-
Use the instruction cd Name of the folder
-
Write mosquitto into the cmd window and it will be ready
-
To see if the connection went well, you can open another cmd window, write down the instruction netstat -an and look for this:
-
Proto Dirección local Dirección remota Estado
-
TCP 0.0.0.0:1883 0.0.0.0:0 LISTENING
The next step is to open the tab with the Node-RED Dashboard.
-
Click on the "Deploy" tag in the Node-RED tab
-
Open a new tab with the following link: http://localhost:1880/ui/#!/0
Your computer screen is going to look similar to this one. (Dashboard display).
If you don't see any change on the Node-RED Dashboard screen, check the IP addresses of the devices that you're using, because they have to be similar such as: computer IP address "10.10.12.1" and PLC IP address "10.10.12.2" and document it in the Arduino IDE file.
Here are the steps that you will have to follow to change both IP addresses:
Go to Network and Sharing Center (Centro de redes y recursos compartidos).
Go to Change Adapter Settings (Cambiar configuraciós del asaptador).