On a recent project, I needed to send some data to PI. As you know, there are a myriad of ways to get data into PI, from using a standard PI interface like PI-OPC or PI-RDBMS through Data Access technologies like PI-OLEDB or PI-ODBC to custom programming solutions like AF SDK or PI Web API.

 

In this case, the source of the data was behind a firewall. We couldn't open port 5450 for various reasons but ports 80/443 were allowed, so PI Web API was definitely available to use to write the data. I elected to write a quick script using VBScript to push the data into PI. It turned out to be not so quick...

 

The initial challenge was to get the web ID for the PI tag(s) involved. This is a PI Web API call, relatively straightforward, but the computer hosting PI Web API has a self-signed certificate which raises a certificate warning. It turns out there's a flag you can set when using the MSXML2.ServerXMLHTTP object to ignore these certificate issues and carry on. The little code snippet to look up a web ID is:

 

Function GetWebID (sTag)
   dim objHTTP
   dim sURL, sWebID, szResponse, iStatus
   dim iPos1, iPos2

   set objHTTP = CreateObject("MSXML2.ServerXMLHTTP")
   objHTTP.SetOption 2, 13056    ' ignore SSL certificate errors
   sURL = WEBAPI_URL_BASE & "/search/query?q=name:" & sTag
   objHTTP.open "GET", sURL, false, WEBAPI_USERNAME, WEBAPI_PASSWORD

   objHTTP.setRequestHeader "User-Agent", "Mozilla/4.0"
   objHTTP.setRequestHeader "Accept", "application/json"
   objHTTP.send

   iStatus = objHTTP.Status
   szResponse = objHTTP.responseText
   if iStatus = 200 then
      iPos1 = Instr(szResponse, "WebID")
      if iPos1 > 0 then
         iPos1 = Instr(iPos1, szResponse, ":") ' find the colon after WebID attribute name
         iPos1 = Instr(iPos1, szResponse, """") ' find the starting quote for the actual WebID
         iPos2 = Instr(iPos1+1, szResponse, """") ' find the ending quote for the actual WebID
         GetWebID = Mid(szResponse, iPos1+1, iPos2-iPos1-1)
      else
         GetWebID = ""
      end if
   else
      GetWebID = ""
   end if
End Function

 

You need to have the constants WEBAPI_URL_BASE defined ("https://server/piwebapi") as well as WEBAPI_USERNAME and WEBAPI_PASSWORD for the username & password. Leave those off if you don't want to use Basic authentication.

 

This code has some issues - it only takes the first web ID, so if more than one is returned from the search, the first is selected.

 

Once you have the web ID, the next job is to write the value at a certain time.

Function WriteValue (sWebID, dLocalTime, sValue)
   dim objHTTP
   dim sURL, szResponse, sUTC
   dim iPos1, iPos2, iBias, iStatus

   iBias = GetMinuteOffsetFromUTC()
   sUTC = ISO8601Date(DateAdd("n", iBias, dLocalTime))

   set objHTTP = CreateObject("MSXML2.ServerXMLHTTP")
   objHTTP.SetOption 2, 13056    ' ignore SSL certificate errors
   sURL = WEBAPI_URL_BASE & "/streams/" & sWebID & "/value"
   objHTTP.open "POST", sURL, false, WEBAPI_USERNAME, WEBAPI_PASSWORD
   objHTTP.setRequestHeader "User-Agent", "Mozilla/4.0"
   objHTTP.setRequestHeader "Content-Type", "application/json"
   objHTTP.send "{""Timestamp"": """ & sUTC & """, ""Value"": """ & sValue & """}"

   WriteValue = objHTTP.Status
End Function

 

You have to convert the time to ISO 8601 format, in UTC time. That took a bit of Googling!

Function GetMinuteOffsetFromUTC()
   dim bias, oShell
   set oShell = CreateObject("Wscript.Shell")
   bias = oShell.RegRead("HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\TimeZoneInformation\ActiveTimeBias")
   GetMinuteOffsetFromUTC = bias
End Function

Function ISO8601Date(dt)
   dim s

   s = DatePart("yyyy",dt) & Right("0" & datepart("m",dt),2) & Right("0" & datepart("d",dt),2)
   s = s & "T" & Right("0" & datepart("h",dt),2) & Right("0" & datepart("n",dt),2) & Right("0" & datepart("s",dt),2)
   s = s & "Z"    ' UTC
   ISO8601Date = s
End Function

 

In summary, once you have the data, you call GetWebID to... get the web ID, then WriteValue to push it to PI.

 

There's a lot more to PI Web API than this, and certainly more efficient ways to do this if you are writing a lot of values, but this is a start. I hope you find this useful.