This blog post is about sending sensor data in an easy, secure and cheap way into the PI Data Archive via a WIFI connection. There are already some blogs about this topics (IoT on the cheap, IoT: Arduino to PI via EventHubs and UFL Connector andArduino To PI with PI Web API), but in it the transmission of data is not done in a secure way (HTTPS) or it makes use of the UFL connector/ interface. I want to send the data with the minimum number of data hops into the PI Data Archive.

 

Integration diagram:

 

Integration Diagram.jpg

As you can see in the integration diagram I have used the following components:

  • The sensor used is a digital temperature and humidity sensor (DHT11). This sensor is not precise, but useful for a PoC and also cheap (less than 1 Euro). Of course you can use other types of sensors.
  • To read the data from the sensor and send it into the data archive I used the ESP8266 as controller. I choose the NodeMCU development board that can be ordered for less than 3 Euros (in China).
  • The router is the internet access point at home.
  • The server is a Windows 2012 R2 server running on Microsoft Azure. On the server I installed AF Server 2015 R2 and PI Data Archive 2016. PI Web API 2015 R2 is used as endpoint.

 

Code:

To read the data from the sensor and write it into the Data Archive I wrote a small piece of code in which I made use of some libraries that I have found on the internet.

 

library

Function

url

DHT.h

Read data from temperature sensor

https://github.com/adafruit/DHT-sensor-library

ArduinoJson.h

Convert data from and to Json format

https://github.com/bblanchon/ArduinoJson

WifiClientSecure.h

Setup a secure connection to the PI WebAPI

https://github.com/esp8266/Arduino/tree/master/libraries/ESP8266WiFi

OSIsoftWebAPI.h

Put values into the PI System via the PI WebAPI

create by myself

 

//  WriteDataToPI.ino - sketch that is receiving Temperature and Pressure form the sensor and write in into PI.
//  Created by Robert Zandvliet; David Golverdingen; Peter van Logchem, August 3, 2016.
//  Released into the public domain.

// Include all libraries that are used anywhere in the sketch. This is an Arduino issue.
// http://provideyourown.com/2011/advanced-arduino-including-multiple-libraries/

#include <Arduino.h>
#include <ArduinoJson.h>
#include <WiFiClientSecure.h>
#include <ESP8266WiFi.h>
#include <ESP8266TrueRandom.h>
#include <OSIsoftWebAPI.h>
#include "DHT.h"

// Serial parameters
const int baudrate = 115200;
// WIFI Parameters
const char* ssid = "your-SSID";
const char* password = "Your-SSID-Password";
// WebApi parameters
char* host = "your-webapi-server";
int httpsPort = 443;
char* AuthenticationID = "your-authenticationID";
const bool debug = false;

OSIsoftWebAPI osisoftWebAPI(host, httpsPort, AuthenticationID, debug);
// DHT sensor parameters
#define DHTPIN D2     // what digital pin we're connected to
#define DHTTYPE DHT11   // DHT 11

// Initialize DHT sensor.
DHT dht(DHTPIN, DHTTYPE);

void setup() 
{
  Serial.begin(baudrate);

  // Connect to the WIFI
  if (!connectToWIFI())
    return;

  delay(10);
  dht.begin();
}

bool connectToWIFI()
{
  int count=0;
  
  Serial.println();
  Serial.println("connecting to " + String(ssid));
  WiFi.begin(ssid, password);

  while (WiFi.status() != WL_CONNECTED && count < 60) 
  {
    delay(500);
    Serial.print(".");
    count++;
  }

  if(count >= 60)
  {
    Serial.println("");
    Serial.println("Unable to connect to " + String(ssid));
    return false;   
  }

  Serial.println("");
  Serial.println("WiFi connected");
  Serial.println("IP address: ");
  Serial.println(WiFi.localIP());

  return true;
}

void loop() 
{  
  // Wait a few seconds between measurements.
  delay(2000);

  // Reading temperature (as Celsius) or humidity takes about 250 milliseconds!
  float h = dht.readHumidity();
  float t = dht.readTemperature();

  Serial.println("");
  Serial.println("Temperature: " + String(t) + "*C ");
  
  // Decalare variable
  QVT qvt;
  qvt.quality = 0;
  qvt.value = t;
  qvt.time = "*";

  // Write value to OSIsoft PI
  if(osisoftWebAPI.PutValue("ESP8266_TestTag", qvt ))
  {
    Serial.println("");
    Serial.println("Temperature succesfully written into OSIsoft PI");
  }
  else
  {
    Serial.println("");
    Serial.println("Unable to write temperature into OSIsoft PI");
  }

  delay(20000);
} 

 

The challenge is to get a secure connection to the PI WebAPI for which I used the WifiClientSecure.h library. At the moment this library only supports basic authentication. So we have to change the authentication mode of the PI WebAPI to basic authentication in AF (screenshot below). When you make use of basic authentication you’ll have to define an AuthenticationID in the code to setup the connection with the PI webAPI.

 

AF Config.png

The OSIsoftWebApi-library is responsible for writing the data to the PI Data Archive. When the function PutValue() (by the code above) is called, it will create a https-request to send the data to the PI Data Archive. After sending the request it is just waiting for a (succesfull) response.

 

#include "Arduino.h"
#include "OSIsoftWebAPI.h"
#include "ArduinoJson.h"
#include "WiFiClientSecure.h"

WiFiClientSecure _client;
int _port;
char* _authenticationID;
char* _host;
bool _debug;

OSIsoftWebAPI::OSIsoftWebAPI(char* host, int port, char* authenticationID, bool debug)
{
  _host = host;
  _port = port;
  _authenticationID = authenticationID;
  _debug = debug;
}

// private functions
String buildHttpsRequest(String command, String url, String data)
{
  String request = command + " " + url + " HTTP/1.1\r\n" +
  "Host: " + _host + "\r\n" +
  "User-Agent: BuildFailureDetectorESP8266\r\n" +
  "Authorization: Basic " + _authenticationID + " \r\n" +
  "Content-Type: application/json\r\n" +
  "Content-Length: " + data.length() + "\r\n" +
  "Connection: close\r\n\r\n" + data;

  return request;
}

bool connectToHost()
{
  Serial.println("connecting to host: " + String(_host) + " (port: " + String(_port) + ")");

  if (!_client.connect(_host, _port)) 
  {
  Serial.println("connection failed");
  return false;    
  }

  delay(250); // give network connection a moment to settle
  return true;
}

String getWebIdFromPIPoint(String pipoint)
{
  // to be retrieved from the WebApi
  String webId = "";
  // ESP8266_TestTag
  webId = "P0tyDbphGW1kuFMr5sqIQDggDgAAAAQVpVUkUtU1ZSMDAxXEVTUDgyNjZfVEVTVFRBRw";
  return webId;
}

bool OSIsoftWebAPI::PutValue(String pipoint, QVT value)
{
  bool result = false;

  String webId = getWebIdFromPIPoint(pipoint);

  // Try to connect to the server that is hosting the WebApi
  if(!connectToHost())
  {
  return result;
  }
  
  String url = "/piwebapi/streams/" + webId + "/value";
  String postData = "{\"Timestamp\": \"" + value.time + "\",\"Value\": " + String(value.value) + "}";
  
  String request = buildHttpsRequest("POST", url, postData);

  _client.print(request);
  Serial.println("request sent to host " + String(_host));
  
  String response = "";
  String chunk = "";
  int limit = 1;
  
  do 
  {
  if (_client.connected()) 
  { 
  yield();
  chunk = _client.readStringUntil('\n');
  response += chunk;
  }
  } 
  while (chunk.length() > 0 && ++limit < 100);  

  Serial.print("Response chunks: " + String(limit) + ", Response code: ");
  
  if (response.length() > 12) 
  { 
  if(response.substring(9, 12) == "202")
  {
  result = true;
  }
  Serial.println(response.substring(9, 12));
  }
  else 
  { 
  Serial.println("Unknown"); 
  } 
  
  return result;
}

 

Because it is a PoC a lot of things can be improved/optimized. I can imagine the following enhancements:

  • Extend the library with a read-data function.
  • Buffering data in the controller when the data cannot be send to the PI Data Archive.
  • The PIPoint that is used is hardcoded, this should be made configurable. Also the WebId that is used by the WebAPI is hardcoded.
  • The SSID and passwords are hardcoded. 
  • Optimizing the code.