Develop your SCADA Application based on NodeRED
Course 10 Chapters of General Content [IS.AC002.GC]
HOW TO INSTALL NODE-RED
NodeRED is
See more information
The first step of this course
HOW TO INSTALL NODEJS :
Just go the official website of NodeJS and download the NodeJS source code or a
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
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
WINDOWS OS:
Type to the command prompt:
npm install -g --unsafe-perm node-red
LINUX OS:
Type to
sudo npm install -g --unsafe perm node-red
Take a look
Once you have installed the Node-RED you are able to proceed with the next chapter of the course,
and now we will see how to install 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.
MQTT client for Arduino based PLC as a I /Os module.
In
Before starting this chapter will be interesting to take a look
After that, take a fast look
-
MQTT: MQTT Post
-
ArduinoJson: ArduinoJson library
The architecture of the code is made for
These arrays are:
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
After explained how to access
In the setup() we just are making the initialization of the Ethernet and MQTT client called
-
OUTPUTS MANAGEMENT:
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,
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
-
INPUT MANAGEMENT:
In the main
In the analog
/*
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);
}
}

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
How to visualize inputs
In the previous chapters we have seen how to install Node-RED in our computer and a useful Arduino code using Industrial Shields units.
So,
The first step
cd /.node-red
Then we will type the command to install Node-RED-Dashboard:
If you want more information about Node-RED-Dashboard
RUNNING NODE-RED
Once Node-RED-Dashboard is installed we can proceed to start out
First of all, we will learn how to execute
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.

Now we are able to see the different Node-RED blocks at the right site. If you click on a module you will
CREATING OUR FIRST PROGRAM
Take one MQTT input blog.
This module will be our MQTT input message from the MQTT broker. Make

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

After converting the message from the MQTT "I" topic to a JavaScript we are ready to analyze the object and divided
-
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

The first split to the object will be the zone.
Node-RED

After the first zone switch, we will

Before placing the final blocks so as to display the values to the dashboard we need to create some groups.
At the right
In the Layout
So, after analyzed and divided the full message, now we just need to show in our dashboard the real and updated value. In
Node-RED also
Take a look

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.




How to interact with Outputs
In
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 outputs architecture will work.
OUTPUTS ARCHITECTURE
As commented before this course is designed to be used with
In our M-Duino 21+ we have 8 digital outputs (from Q0_0 to Q0_7), apart of that we know that three of these digital outputs can work as
ADDING A NEW TAB
To have a better organization of our dashboard will be
So, 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 site select the menu and click on flow -- > Add a new flow. Then go to renamed

After

Now we are ready to add the dashboard items. Add a Switch and a Slider. Click on them and named. 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.



SENDING THE MQTT MESSAGE
Since here we have placed the items to our

* For every different input we must set the index value. In this
The change function will add

* For every different zone we must set the proper value of the zone. In this
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
* Remember that we have added the topic into the switch function. We can also
Take a look


Our Node-RED program should look like:
