Editor's Note: The Industrial Internet of Things (IIoT) promises to provide deep insight into industrial operations and enhance efficiency of connected machines and systems. Large-scale IIoT applications rely on layered architectures to collect data from a broad range of sensors, move data reliably and securely to the cloud, and perform analysis required to deliver that insight and efficiency. In Industrial Internet Application Development, the authors provide a detailed examination of the IIoT architecture and discuss approaches for meeting the broad requirements associated with these systems.
Chapter 3. IIoT Edge Development (Continued)
By Alena Traukina, Jayant Thomas, Prashant Tyagi, Kishore Reddipalli
Industrial M2M protocols – OPC UA
In this section, we will try to build a simple IoT app for sending data from a sensor simulator module to a receiver device (a PC or a cloud), using a Raspberry Pi hub and the OPC UA protocol:
Data flow from a sensor simulator to a receiver deviceThe OPC UA protocol is similar to Modbus, but works with more data types, and has no serious limitations, while providing for security, compression, and low latency.
广告
The protocol was developed by the OPC Foundation as an industrial machine-to-machine communication protocol. OPC UA (Unified Architecture) is an improved version of the Open Platform Communications (OPC ) protocol, with one of the major changes being that the new protocol is available free of charge without any restrictions.
In the following table, you can find a more detailed description of the protocol to understand whether it is suitable for your needs:
Key | Value |
Open source | Yes |
The OSI layer | Transport or application |
Data types | Integer, float, string, Boolean, date, time, and so on |
Limitations | Not suitable for a complex architecture |
Possible operations | Read/write/monitor/query variables |
Latency | Low |
Usage | IIoT |
Security | Yes |
Compression | Yes |
Table 5: OPC UA protocol specifications
For building the application, we will need the following:
- Required software
- Node.js 6+ (https://nodejs.org/en/download/)
- PostgreSQL (https://www.postgresql.org/download/)
- The Cloud Foundry CLI (https://github.com/cloudfoundry/cli#downloads)
- Request (https://www.npmjs.com/package/request)
- NodeOPCUA (https://www.npmjs.com/package/node-opcua)
- Async (https://www.npmjs.com/package/async)
- Docker (https://docs.docker.com/engine/installation/)
- Required hardware
- Raspberry Pi 3 (model B)
- A power adapter (2A/5V)
- A microSD card (8 GB+) and an SD adapter
- Ethernet cable for a wired network connection
Preparing an SD cardTo prepare an SD card, follow the sequence of actions as described:
Connect your SD card to a computer and use Etcher (
https://io/) to flash the Raspbian .img file to the SD card.
Enable SSH:
cd /Volumes/boot touch ssh
network={
ssid=”YOUR_SSID”
psk=”YOUR_WIFI_PASSWORD”
}
 | To create a file in a Linux console, you can use the GNU nano editor. It is pre-installed in most Linux distributives. All you need is to run the nano FILE_NAME command and follow the displayed instructions.
|
{
“name”: “hub”,
“version”: “1.0.0”,
“description”: “”,
“main”: “index.js”, “scripts”: {
“start”: “node index.js”,
“test”: “echo “Error: no test specified” && exit 1″
},
“author”: “”,
“license”: “ISC”, “dependencies”: {
“async”: “^2.4.0”,
“node-opcua”: “0.0.64”,
“request”: “^2.81.0”
}
}
REMOTE-SERVER-ADDRESS.com and REMOTE-SENSOR-ADDRESS with real values:
var opcua = require(“node-opcua”); var async = require(“async”);
var request = require(“request”);
var session, subscription;
var client = new opcua.OPCUAClient();
var sensor = “opc.tcp://REMOTE-SENSOR- ADDRESS:4334/UA/resourcePath”;
var receiver = “http://REMOTE-SERVER-ADDRESS.com:8080”;
async.series( [
// establishing connection function (cb) {
client.connect(sensor, function (err) {
if (err) {
console.log(“Connection to ” + sensor + “failed”);
} else {
console.log(“Connection successful”);
}
cb(err);
});
},
// start session function (cb) {
client.createSession(function (err, res) {
if (!err) session = res;
cb(err);
});
},
// read value
function (cb) {
session.readVariableValue(“ns=1;s=Variable1”, function (err, dataValue) {
if (!err) console.log(“Variable1 = “, dataValue.value.value);
cb(err);
});
},
// write value
function (cb) {
session.writeSingleNode(“ns=1;s=Variable1”, new opcua.Variant({
dataType: opcua.DataType.Double, value: 100
}), function (err) {
cb(err);
});
},
// subscribe to changes
function (cb) {
subscription = new opcua.ClientSubscription(session, {
maxNotificationsPerPublish: 5,
priority: 5,
publishingEnabled: true,
requestedLifetimeCount: 5,
requestedMaxKeepAliveCount: 3,
requestedPublishingInterval: 500,
});
subscription.on(“started”, function () {
console.log(“subscription id: “,
subscription.subscriptionId);
}).on(“terminated”, function () {
cb();
});
setTimeout(function () {
subscription.terminate();
}, 5000);
// install monitored item
var monitor = subscription.monitor({
attributeId: opcua.AttributeIds.Value,
nodeId: opcua.resolveNodeId(“ns=1;s=Variable1”),
},
{
discardOldest: true,
samplingInterval: 50,
queueSize: 5,
},
opcua.read_service.TimestampsToReturn.Both
);
monitor.on(“changed”, function (dataValue) {
console.log(“Variable1 = “, dataValue.value.value);
// send to receiver
var data = {
device: “sensor1”,
timestamp: Date.now(),
Variable1: dataValue.value.value
};
request.post({url: receiver, form: data}, function (err) {
if (err) console.log(“Failed to send ” +
JSON.stringify(data) + ” to ” + receiver);
});
});
},
// close session
function (cb) {
session.close(function (err) {
if (err) console.log(“Failed to close session”); cb();
});
}
],
function (err) { if (err) {
console.log(“Failed with error:”, err);
} else {
console.log(“Successfully finished”);
}
client.disconnect(function () {
});
}
); FROM hypriot/rpi-node:boron-onbuild
{
“name”: “sensor”,
“version”: “1.0.0”,
“description”: “”,
“main”: “index.js”,
“scripts”: {
“start”: “node index.js”,
“test”: “echo “Error: no test specified” && exit 1″
},
“author”: “”,
“license”: “ISC”,
“dependencies”: {
“node-opcua”: “0.0.64”
}
}
var opcua = require(“node-opcua”);
var min = 1;
var max = 100;
var host = new opcua.OPCUAServer({ buildInfo: {
buildDate: new Date(2018, 8, 8),
buildNumber: “1234”,
productName: “productName”,
},
port: 4334,
resourcePath: “UA/resourcePath”,
});
host.initialize(function () {
var space = host.engine.addressSpace;
var componentOf = space.addObject({
browseName: “browseName”,
organizedBy: space.rootFolder.objects,
});
var variable1 = 0;
// generate new value
setInterval(function () {
variable1 = Math.floor(max – Math.random() * (max – min));
}, 500);
space.addVariable({
browseName: “browseName”,
componentOf: componentOf,
dataType: “Double”,
nodeId: “ns=1;s=Variable1”, // a string nodeID
value: {
get: function () {
return new opcua.Variant({dataType: opcua.DataType.Double, value: variable1});
},
set: function (variant) {
variable1 = parseFloat(variant.value);
return opcua.StatusCodes.Good;
}
}
});
host.start(function () {
var endpoint =
host.endpoints[0].endpointDescriptions()[0].endpointUrl; console.log(“Endpoint: “, endpoint);
});
});
/home/pi/sensor/index.js file.
FROM hypriot/rpi-node:boron-onbuild
Running a simulator application on an RPiTo run a simulator on an RPi, proceed as the following steps suggest:
Insert an SD card into the
Connect an Ethernet cable and open an SSH connection.
Navigate to /home/pi/sensor.
# Build an image from a Dockerfile
docker build -t opcua-sensor .
#
# Run container in foreground
docker run -p 4334:4334 –privileged -it –rm –name opcua-sensor- container opcua-sensor
#
# Run container in background
# docker run -p 4334:4334 –privileged -d –rm –name opcua- sensor-container opcua-sensor
#
# Fetch the logs of a container
# docker logs -f opcua-sensor-container #
# Stop running container
# docker stop opcua-sensor-container
Console output when a simulator app is running Running a receiver application on a PCTo run a receiver app on a PC, follow the sequence described here:
docker run –rm –name postgres-container -e POSTGRES_PASSWORD=password -it -p 5433:5432 postgres
docker exec -it postgres-container createdb -U postgres iot-book
{
“name”: “receiver”,
“version”: “1.0.0”,
“description”: “”,
“main”: “index.js”,
“scripts”: {
“start”: “node index.js”,
“test”: “echo “Error: no test specified” && exit 1″
},
“author”: “”,
“license”: “ISC”,
“dependencies”: {
“pg”: “^6.2.3”
}
}
var restify = require('restify');
var server = restify.createServer({name: 'MyApp'});
server.use(restify.bodyParser());
var Pool = require('pg').Pool;
var pool = new Pool({
database: 'iot-book',
host: 'host',
password: 'password',
port: 5433,
user: 'postgres',
});
//ensure table exists in db
pool.query('CREATE TABLE IF NOT EXISTS “sensor-logs” (id serial NOT NULL PRIMARY KEY, data json NOT NULL)', function (err, result) {
if (err) console.log(err);
});
server.post('/', function create(req, res, next) {
console.log(req.params);
//save in db
pool.query('INSERT INTO “sensor-logs” (data) VALUES ($1)', [req.params], function (err, result) {
if (err) console.log(err);
res.send(201, result);
});
return next();
});
server.get('/stats', function search(req, res, next) {
pool.query('SELECT AVG(“Variable1”), MAX(“Variable1”),
MIN(“Variable1”), COUNT(*), SUM(“Variable1”) FROM (SELECT
(data->>'Variable1')::int “Variable1” FROM “sensor-logs” ORDER BY id DESC LIMIT 10) data', function (err, result) {
if (err) console.log(err); res.send(result.rows);
});
return next();
});
server.listen(process.env.PORT || 8080);
FROM node:boron-onbuild EXPOSE 8080
# Build an image from a Dockerfile
docker build -t opcua-receiver .
# Run container in foreground
docker run -p 8080:8080 -it –rm –name opcua-receiver-container opcua-receiver
# Run container in background
# docker run -p 8080:8080 -d –rm –name opcua-receiver-container opcua-receiver
# Fetch the logs of a container
# docker logs -f opcua-sensor-container
# Stop running container
# docker stop opcua-receiver-container
Console output when a receiver app is running Running a receiver application in PredixTo run a receiver app in Predix, follow this sequence:
applications:
–
name: receiver
memory: 128M
random-route: true
cf push
Running a hub application on an RPiTo run a hub application on an RPi, proceed as follows:
# Build an image from a Dockerfile
docker build -t opcua-hub .
#
# Run container in foreground
docker run –privileged -it –rm –name opcua-hub-container opcua- hub
#
# Run container in background
# docker run –privileged -d –rm –name opcua-hub-container opcua-hub
#
# Fetch the logs of a container
# docker logs -f opcua-hub-container
#
# Stop running container
# docker stop opcua-hub-container
Console output when a hub app is running Getting statisticsTo get statistics on sensor data, one needs to open a browser and navigate to
http://RECEIVER-ADDRESS:8080/stats or https://RECEIVER-IN-PREDIX/stats:
[
{
“avg”: “64.3”,
“max”:100,
“min”:17,
“count”:”10″,
“sum”:”643″
}
]
Reprinted with permission from Packt Publishing. Copyright © 2018 Packt Publishing
About the authors
Alena Traukina is IoT practice Lead at Altoros. She has over 12 years of experience in delivery and support of business-critical software applications and is one of the first GE's Predix Influencers.
Jayant Thomas (JT) is the director of software engineering for the IoT apps for GE Digital. He is responsible for building IoT SaaS applications using the Predix platform, and specializes in building microservices-based architecture, reactive, event-driven systems.
Prashant Tyagi is responsible for enabling the big data strategy at GE Digital for the Industrial Internet that leverages IT and Operational data for predictive analytics. He works with all the P&L verticals (such as oil and gas, power generation, aviation, healthcare, and so on) to enable their IoT use cases on the data and analytics platform.
Kishore Reddipalli is a software technical director and expert in building IIoT big data and cloud computing platform and products at ultra scale. He is passionate in building software for analytics and machine learning to make it simplified for authoring the algorithms from inception to production at scale.