IIoT edge development – Using OPC UA protocols

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. 

Adapted from Industrial Internet Application Development, by Alena Traukina, Jayant Thomas, Prashant Tyagi, Kishore Reddipalli.


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 device

The 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 card

To prepare an SD card, follow the sequence of actions as described:

  1. Download the latest Raspbian LITE image (available at https:// raspberrypi.org/downloads/raspbian/).

  2. Connect your SD card to a computer and use Etcher (https://io/) to flash the Raspbian .img file to the SD card.

  3. Enable SSH:

cd /Volumes/boot touch ssh

  1. To enable Wi-Fi, create conf with the following content:

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.
  1. Create the /home/pi/hub

  2. Create the /home/pi/hub/package.json file with the following content:

{
   “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”
   }
}

  1. Create the /home/pi/hub/index.js file with the following content, replacing

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 () {
});
}
);

  1. Create the /home/pi/hub/Dockerfile file with the following content:

FROM hypriot/rpi-node:boron-onbuild

  1. Create the /home/pi/sensor

  2. Create the /home/pi/sensor/package.json file with the following content:

{
   “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”
   }
}

  1. Create the /home/pi/sensor/index.js file with the following content:

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);
   });
});

  1. Configure the min and max values at the beginning of the

/home/pi/sensor/index.js file.

  1. Create the /home/pi/sensor/Dockerfile file with the following content:

FROM hypriot/rpi-node:boron-onbuild

Running a simulator application on an RPi

To run a simulator on an RPi, proceed as the following steps suggest:

  1. Insert an SD card into the

  2. Connect an Ethernet cable and open an SSH connection.

  3. Navigate to /home/pi/sensor.

  1. Build an image and run a Docker container:

# 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 PC

To run a receiver app on a PC, follow the sequence described here:

  1. Install and launch a PostgreSQL container:

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

  1. Create the receiver

  1. Create the ./receiver/package.json file with the following content:

{
   “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”
   }
}

  1. Create the ./receiver/index.js file with the following content, replacing the database credentials with the correct values:

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);
   

  1. Create the ./receiver/Dockerfile file with the following content:

FROM node:boron-onbuild EXPOSE 8080

  1. Build an image and run a Docker container:

# 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 Predix

To run a receiver app in Predix, follow this sequence:

  1. Install and connect the Сloud Foundry CLI to your Predix

  2. Create a PostgreSQL service and obtain the

  1. Create the ./receiver/manifest.yml file with the following content:

applications:

name: receiver
memory: 128M
random-route: true

  1. Replace the database credentials in ./receiver/index.js.

  2. Deploy to the cloud:

cf push

  1. Change the REMOTE-SERVER-ADDRESS in the hub application on the RPi to the newly deployed

Running a hub application on an RPi

To run a hub application on an RPi, proceed as follows:

  1. Open an SSH

  2. Navigate to /home/pi/hub.

  3. Build an image and run a Docker container:

# 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 statistics

To 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.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.