Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / VB

Data Historians - You Bought It, Use It! Real World Example

4.97/5 (17 votes)
23 Jan 2011CPOL11 min read 78.4K  
Getting value from your data historian and recovering data using ProcessBook

Introduction

No matter what line of business you are in, it is all about data, lots of data, data from every source possible. The data can be collected manually, i.e., by someone writing down information onto a sheet of paper, or by punching numbers into spreadsheets or data collectors, or it can be collected automatically using automation systems such as control system historians or siphoned off into other system by specialist data repositories.

As a oil producing platform, we have data everywhere, from basic water system pressures, diesel inventories, oil producing well test data, even data on the current weather conditions. And the majority of the data is collected. There has been many a time I have sat back and thought to myself, why are we collecting this, what benefit it is? We often collect data just in case it might be useful one day, and yes, that does happen.

We even spend vast amounts of money on systems just to do the collecting, and then as time advances, figure out what we can and will do with all the information.

This article is an example of how some basic mathematics and raw data was put to use in a real world example I implemented recently.

Background

Over the last few years, we have been upgrading and replacing our controls systems to DeltaV, which included replacing the field instrumentation that performs all the process measurement and control. The DeltaV system has its own local data historian which collects the process parameters and records any alarm/events and operator interaction. Now with the system being located in the middle of the North Sea, it doesn't make it very easy for the onshore engineering teams to access the data. We did implement operator stations in the onshore control room, but with the risk of a non qualified person accidentally shutting down the operation, access to these workstations are restricted, and there are many people wanting access to the process data.

Add to this that there is a DeltaV system installed on each of the other platforms in the field, as well as other standalone systems, e.g., gas compressors and PLC based control systems and you end up with data fragmented across the field. If you then start to interconnect these platforms with subsea pipelines for transferring Oil and Gas, Produced Water and Electricity around the field, it could become difficult to marry up the data across the field and see the direct interaction of all the disparate processes in realtime.

The solution to this problem was to then introduce a standalone specialist data historian. This is capable of sucking all the data required from the various systems and centralising it, and making it available to all and sundry who want the information. This gives you a key segregation/security layer between the live control systems and the data.

Forties Field

Figure 1: Forties Field Platform Layout and Infrastructure

Down to Business - Putting the Historian to Work

Here we are, spending millions every year getting the oil out of the ground. As you could imagine, any dowtime immediately starts to cost you in lost revenue. Two of the main issues that can very rapidly start to impact plant operations are corrosion of pipework and scale deposition within the pipework. To manage both of these factors, chemical injection plays a key role in ensuring the product stays in the pipes and can flow freely. See the picture and text contained in this article to see and read how quickly scale can form, it will surprise you! Not only does it cost you in lost revenue, but the cost of repair can also start to become really expensive.

As a method of mitigating the impact of both corrosion and scale, chemicals are injected into the process. There are many other things we use chemicals, for e.g., oxygen scavaging, demulsification, etc., but on my installation, the two main ones are the corrosion inhibitor and scale inhibitor. These chemicals are stored onboard and injected automatically.

Firstly, the operations team conduct regular well testing to determine the oil and water volumes produced, and this information is used by the onshore chemists to determine the optimum rate for injection of the relevant chemical. The chemicals are expensive and obviously over injecting can waste money, but can also have an adverse effect on the process, so getting this number right is important. This injection rate is then entered into the control system which then regulates the flow of the chemical. As stocks onboard the installation are depleted, new shipments are ordered and decanted into the local platform chemical storage vessels.

As chemistry management is of such a key importance, we record in the control system historian and the central historian the levels in the storage tank, as well as the injected flow rates. Great, now we are recording the data. What will we do with it? How can we make use of the data?

With the recorded injection flowrates, we can immediately see if the injected chemicals stop for any reason, maybe the injection quill has blocked up or the injection pump as failed. We could also in theory get a flow totaliser of the injected chemicals.

Along comes the onshore platform engineer and asks, "Can I calculate how much volume of chemical I have in the storage vessel at any given time? Can I also determine how much chemical we have used in the last fiscal 24hr period?" This information can then be used to see if the performance of the injection system is meeting expectations and also allow a cross check of utilisation versus procurement of chemical.

The engineer then duly hands me some equations he has obtained from the process engineers relating to standard vessel volume calculations and wants to know if these can be used in some way automatically.

Head Scratching Time - Putting It Together

From the engineering data, we have the vessel dimensions, the location information on the vessel of the level instrumentation and measured range limits of the level transmitters. Contained within the historian, we have the percentage of the measured range stored. Using basic mathematics, it is therefore possible to determine the volumes of chemical stored within the vessel. Let's take a look at this vessel;

Storage Vessel

Figure 2: Chemical Storage Vessel

As you can see in Figure 2, the vessel has 3 storage compartments, each one is independent, each one being for a different chemical. As can be seen, Compartment 1 consists of a dome end and a cylindrical section, Compartment 2 is just a cylindrical section. The Percentage shown in each compartment represents the historian value of the indicated level and a representative bar graph (ok, I admit, not quite to scale!) The engineering dimensional data we have available and required by the calculations are:

  • D = The vessel internal diameter
  • L = The cylindrical section length
  • b = The dome section length
  • Is = Instrument measured start point height
  • Ih = Instrument measured height

Let's concentrate on Compartment 1, as this consists of both a Dome End and a Cylinder section, Compartment 2 would only need the cyclindrical section from the calculations.

The volume in the Compartment 1 can be summarised as; Volume Total = Volume End + Volume Cylinder; With all dimensional data used in the calculations in metres (m), the volume will be in cubic metres (m3), therefore;

PuttingHistorianToUse/formula_1.png
where:

PuttingHistorianToUse/formula_2.png

Now, we are only interested in partial volumes based on the liquid level, to achieve this, we need some additional equations:

Dome End Factor:

PuttingHistorianToUse/formula_3.pngPuttingHistorianToUse/formula_4.png

Cylinder Factor:

PuttingHistorianToUse/formula_5.png ; PuttingHistorianToUse/formula_6.png

and:

PuttingHistorianToUse/formula_7.png;

Note: a is in radians

For both Factors,

PuttingHistorianToUse/formula_8.png

Let's work an example just to make sure where are all in the same place! For Compartment 1, the dimensions are:

  • Vessel ID: 2m
  • Cylinder Length: 0.9m
  • Dome Length: 0.408m
  • Instrument start: 0.16m
  • Instrument range: 1.84m
  • Indicated Level: 60% [yes, I know it shows 45.7% in the sketch, but will stick to whole numbers!]

So, start by obtaining H1 = 0.16 + (1.84 * (60/100)) = 1.264m.

Volume in Cyclinder:
a = 2 * Atan(1.264 / v(((2 * 1.264 * 2)/2) -(1.264)2)) => 2 * Atan(1.31) = 1.84

F(Zc) = (1.84 - sin(1.84) * cos(1.84)) / p
F(Zc) = 0.67

Volume in Cyclinder = 1/4 p D2LF(Zc) => (22 * 0.90 * 0.67 * p) / 4 => 1.89m3

Volume in Dome:
K1 = b/D => 0.408/2 = 0.204
F(Ze) = -(1.264 / 2)2 * ( -3 + ((2*1.264)/2)) => -(0.399)*(-1.736) = 0.69

Volume in Dome End = 1/6 * p * 0.204 * 23 * 0.69 = 0.59m3

Total Volume = Dome + Cylinder = 1.89 + 0.59 = 2.48m3

Implementing in Code on the Historian Client Tools

Now that we have the necessary formula and worked data, we can concentrate on turning this into something useful.

The data historian that we purchased and implemented is an OSISoft PI system, and the client tool we used for this example is called ProcessBook. One thing I have noticed whilst working with this is how very little information is available on the net. Normally user groups sprout up and everyone shares code snippets, etc., but with these products, it is extremely hard to find anything. This was another reason for wanting to share this here, maybe help to get more of the user community sharing.

ProcessBook can be programmed using Visual Basic for Applications. When ProcessBook is installed, the PISystem SDK is installed with all the necessary libraries and services required to access the data stored within the PI historian. The user will configure connections to any number of PI Servers and also will logon as required with their security credentials which will then permit them access to their authorised data sets.

A new ProcessBook display was created which contains the graphics showing realtime data, display text and a button. When the user clicks the button, the VBA code is fired off, which obtains the current Level Indication used for the current snapshot volume. It also determines the last fiscal 24hour period, then obtains the historical level information and uses that to determine the overall volume of chemical used during that period.

The equations used above were broken down into functions that can be called within the code as required.

When the user clicks the buttons, it fires of the function DoCalcs();

VB.NET
Public Sub DoCalcs()
On Error GoTo Error_Handler                         'Set up an Error Handler

There are a few PI System specific objects that need to be defined to grab the data from the historian server, the server object and 2 data point objects:

VB.NET
Dim currentServer As Server
Dim scaleTag As PIPoint
Dim corrTag As PIPoint

'Get the current Default Server for the PB
Set currentServer = Servers.DefaultServer

'Set up the 2 Chemical Points
Set scaleTag = currentServer.PIPoints.Item("LI1T605E/PV.CV")
Set corrTag = currentServer.PIPoints.Item("LI1T604E/PV.CV")

Next, we go and grab the current snapshot realtime value which we can then use to determine the current snapshot volume in the compartments:

VB.NET
'Get the current Snapshot Values + Time Stamp
Dim scaleLevel As Single: scaleLevel = CSng(scaleTag.Data.Snapshot.Value)
TextScaleSnapshot.Contents = scaleLevel
TextScaleSnapshotTime.Contents = scaleTag.Data.Snapshot.TimeStamp.LocalDate
Dim scaleVol As Single
scaleVol = CalculateScaleVolume(scaleLevel) * 1000     '1000Litres = 1m3
textScaleSnapshotVolume.Contents = scaleVol

Dim corrLevel As Single: corrLevel = CSng(corrTag.Data.Snapshot.Value)
TextCorrSnapshot.Contents = corrLevel
TextCorrSnapshotTime.Contents = corrTag.Data.Snapshot.TimeStamp.LocalDate
Dim corrVol As Single
corrVol = CalculateCorrossionVolume(corrLevel) * 1000
textCorrSnapshotVolume.Contents = corrVol

The next part of the code determines the previous fiscal 24hour period. On our installation, we have a reporting fiscal period of 18:00hrs to 18:00hrs, i.e. a Day starts at 6pm in the evening and runs through the night until the next day's 6pm.

VB.NET
'24-Hour Fiscal Period Dates
Dim fiscalPeriodStartDate As Date
Dim fiscalPeriodEndDate As Date
fiscalPeriodStartDate = returnPreviousStartDate(CDate_
	(scaleTag.Data.Snapshot.TimeStamp.LocalDate))
fiscalPeriodEndDate = returnPreviousFinishDate(CDate_
	(scaleTag.Data.Snapshot.TimeStamp.LocalDate))
textPeriodStartDate.Contents = fiscalPeriodStartDate
textPeriodFinishDate.Contents = fiscalPeriodEndDate

The recorded data values are grabbed from the historian from the fiscal period, and the start and end values are used to calculate the volumes used during that fiscal period. The historian will interpolate the data it has and provide a value for the start time and end time, if an exact reading does not exist at that instance in time. It then updates the user display with the information it has and how many data values have been found for the period. It repeats this for both the compartments (Scale and Corrosion).

VB.NET
Dim scaleValues As PIValues
Set scaleValues = scaleTag.Data.RecordedValues_
(CDate(textPeriodStartDate.Contents), CDate(textPeriodFinishDate.Contents), btAuto)

If scaleValues.Count > 0 Then
    TextScaleRecordCount.Contents = scaleValues.Count & _
	" Recorded values for fiscal period."
    Dim scalePeriodStartValue As Single: scalePeriodStartValue = _
	scaleValues.Item(1).Value
    Dim scalePeriodFinishValue As Single: scalePeriodFinishValue = _
	scaleValues.Item(scaleValues.Count).Value
    TextScalePeriodStartValue.Contents = scalePeriodStartValue
    TextScalePeriodStartTime.Contents = scaleValues.Item(1).TimeStamp.LocalDate
    textScalePeriodStartVolume.Contents = _
	CalculateScaleVolume(scalePeriodStartValue) * 1000
    TextScalePeriodFinishValue.Contents = scalePeriodFinishValue
    TextScalePeriodFinishTime.Contents = _
	scaleValues.Item(scaleValues.Count).TimeStamp.LocalDate
    textScalePeriodFinishVolume.Contents = _
	CalculateScaleVolume(scalePeriodFinishValue) * 1000
    textScalePeriodVolume.Contents = Val(textScalePeriodStartVolume.Contents) - _
	Val(textScalePeriodFinishVolume.Contents)
Else
    TextScaleRecordCount.Contents = "0 Recorded values for fiscal period."
    TextScalePeriodStartValue.Contents = "0"
    TextScalePeriodStartTime.Contents = "No Data"
    textScalePeriodStartVolume.Contents = "0"
    TextScalePeriodFinishValue.Contents = "0"
    TextScalePeriodFinishTime.Contents = "No Data"
    textScalePeriodFinishVolume.Contents = "0"
    textScalePeriodVolume.Contents = "0"
End If

Dim corrValues As PIValues
Set corrValues = corrTag.Data.RecordedValues(CDate(textPeriodStartDate.Contents), _
	CDate(textPeriodFinishDate.Contents), btAuto)

If corrValues.Count > 0 Then
    Dim corrPeriodStartValue As Single: corrPeriodStartValue = corrValues.Item(1).Value
    Dim corrPeriodFinishValue As Single: corrPeriodFinishValue = _
	corrValues.Item(corrValues.Count).Value
    TextCorrRecordCount.Contents = corrValues.Count & _
		" Recorded values for fiscal period."
    TextCorrPeriodStartValue.Contents = corrPeriodStartValue
    TextCorrPeriodStartTime.Contents = corrValues.Item(1).TimeStamp.LocalDate
    textCorrPeriodStartVolume.Contents = _
	CalculateCorrossionVolume(corrPeriodStartValue) * 1000
    TextCorrPeriodFinishValue.Contents = corrPeriodFinishValue
    TextCorrPeriodFinishTime.Contents = _
	corrValues.Item(corrValues.Count).TimeStamp.LocalDate
    textCorrPeriodFinishVolume.Contents = _
	CalculateCorrossionVolume(corrPeriodFinishValue) * 1000
    textCorrPeriodVolume.Contents = Val(textCorrPeriodStartVolume.Contents) - _
	Val(textCorrPeriodFinishVolume.Contents)
Else
    TextCorrRecordCount.Contents = "0 Recorded values for fiscal period."
    TextCorrPeriodStartValue.Contents = "0"
    TextCorrPeriodStartTime.Contents = "No Data"
    textCorrPeriodFinishVolume.Contents = "0"
    TextCorrPeriodFinishValue.Contents = "0"
    TextCorrPeriodFinishTime.Contents = "No Data"
    textCorrPeriodFinishVolume.Contents = "0"
    textCorrPeriodVolume.Contents = "0"
End If

Exit Sub
Error_Handler:
MsgBox "Error Calculating: " & Err.Description

End Sub

Two helper functions for calculating the fiscal period start and end date/time:

VB.NET
'This function will return a startdate used by the previous working fiscal period
Private Function returnPreviousStartDate(snapshotdate As Date) As Date

Dim workingdatetime As Date
workingdatetime = CDate(Format(snapshotdate, "YYYY-MMM-DD") & " 18:00:00")

If DateDiff("s", snapshotdate, workingdatetime) <= 0 Then
    returnPreviousStartDate = DateAdd("d", -1, workingdatetime)
Else
    returnPreviousStartDate = DateAdd("d", -2, workingdatetime)
End If

End Function

'and the finishdate for the fiscal period
Private Function returnPreviousFinishDate(snapshotdate As Date) As Date
Dim workingdatetime As Date
workingdatetime = CDate(Format(snapshotdate, "YYYY-MMM-DD") & " 18:00:00")

If DateDiff("s", snapshotdate, workingdatetime) <= 0 Then
    returnPreviousFinishDate = workingdatetime
Else
    returnPreviousFinishDate = DateAdd("d", -1, workingdatetime)
End If
End Function

We then have the 2 calculation functions, one for the Scale (which has the dome end) and one for the Corrosion:

VB.NET
'The following Function Calculate the Vessel Volumes for each compartment
'Note: We will use Single Precision Floating Points for all calcs
'Note: All vessel measurements to be in meters

Private Function CalculateCorrossionVolume(theLevel As Single) As Single
'This is the cylinder section in the middle of the vessel
'NO DOME ENDS

'All measurements in metres except indicated level in %
'result is cubic metres

On Error GoTo Error_Handler
'First we need to know the internal ID of the vessel in metres
    'Final result
    Dim result As Single: result = 0
    
    'Set dimension elements
    Dim cylLength As Single: cylLength = 2.8
    Dim cylDiam As Single: cylDiam = 2
    Dim instStart As Single: instStart = 0.16
    Dim instLength As Single: instLength = 1.84
    
    Dim pi As Single: pi = 3.14159265358979
    
    'Get vessel Level in %
    Dim level As Single: level = theLevel
    
    'Use the % level to calculate actual level, reuse the level variable for result
    level = instStart + (instLength * (level / 100)) ' level now in metres
    
    'Volume of liquid in Cylinder is
    result = (Zc(level, cylDiam) * cylLength * (cylDiam) ^ 2 * pi) / 4
    
    CalculateCorrossionVolume = result

    Exit Function

Error_Handler:

    CalculateCorrossionVolume = 0
    
    MsgBox "Error with Corrossion Calc: " & Err.Description

End Function


Private Function CalculateScaleVolume(theLevel As Single) As Single
'Dome Ended Cylinder
 
'All measurements in metres except indicated level in %
'result is cubic metres

On Error GoTo Error_Handler
'First we need to know the internal ID of the vessel in metres
    'Final result
    Dim cylResult As Single: cylResult = 0
    Dim domeResult As Single: domeResult = 0
    
    'Set dimension elements
    Dim cylLength As Single: cylLength = 0.9
    Dim cylDiam As Single: cylDiam = 2
    Dim instStart As Single: instStart = 0.16
    Dim instLength As Single: instLength = 1.84
    Dim domeLength As Single: domeLength = 0.408
    
    Dim pi As Single: pi = 3.14159265358979
    
    'Get vessel Level in %
    Dim level As Single: level = theLevel
    
    'Use the % level to calculate actual level, reuse the level variable for result
    level = instStart + (instLength * (level / 100)) ' level now in metres
    
    'Volume of liquid in Cylinder is
    cylResult = (Zc(level, cylDiam) * cylLength * (cylDiam) ^ 2 * pi) / 4
    
    'Volume of liquid in dome is
    domeResult = (Ze(level, 2) * (cylDiam ^ 3) * (domeLength / cylDiam) * pi) / 6
    
    CalculateScaleVolume = cylResult + domeResult
    
    Exit Function

Error_Handler:

    CalculateScaleVolume = 0
    
    MsgBox "Error with Scale Calc: " & Err.Description

End Function

The function below is used to determine a factor used for the partial volume in the dome end and takes the liquid height and vessel diameter as input values:

VB.NET
 Private Function Ze(ByVal liquidHeight As Single, ByVal vesselDiam As Single) As Single
    'This is part of the Dome End Calculation
    
    Dim result As Single
    result = 0 - ((liquidHeight / vesselDiam) ^ 2)

    result = result * (-3 + ((2 * liquidHeight) / vesselDiam))
    
    Ze = result

End Function

The function below is used to determine a factor used for the partial volume in the cylinder and takes the liquid height and vessel diameter as input values:

VB.NET
Private Function Zc(ByVal liquidHeight As Single, ByVal vesselDiam As Single) As Single
    'This is part of the Cylinder Calculation
    
    Dim result As Single
    Dim alpha As Single
    Dim pi As Single: pi = 3.14159265358979
    
    alpha = getAlpha(liquidHeight, vesselDiam)
        
    result = (alpha - (Sin(alpha) * Cos(alpha))) / pi
    
    Zc = result
    
End Function

The function below is one used to calculate a and takes the liquid height and vessel diameters as input values:

VB.NET
Private Function getAlpha(ByVal liquidHeight As Single, _
	ByVal vesselDiam As Single) As Single
    'This is used by Zc function
    
    Dim result As Single
    
    result = Sqr(((2 * liquidHeight * vesselDiam) / 2) - (liquidHeight ^ 2))
    
    result = liquidHeight / result
    
    result = CSng(2 * Atn(CDbl(result)))
    
    getAlpha = result
    
End Function   

What We End Up With

So when the user clicks the buttons and the code runs, the graphical components are updated with the information required. Below is a snapshot for the display I implemented, the code also converts from the m3 to litres, as this helps to tie up the injection rates against the usage rates.

ProcessBook Display

Figure 3: ProcessBook Display Implementation

So there you have it, how to make use of historical data to help manage your chemical policies, complete with some code on how to grab this data if you are using an Enterprise class OSISoft PI Historian and ProcessBook client tools.

Points Of Interest

The Google Chart API was used to create the images for the formula, see here for more information.

History

  • 23rd January, 2011 - Replaced text formula with images 
  • 15th January, 2011 - First article release

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)