Click here to Skip to main content
14,933,402 members
Articles / Programming Languages / Visual Basic
Posted 14 Jan 2011

Tagged as


23 bookmarked

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

Rate me:
Please Sign up or sign in to vote.
4.97/5 (17 votes)
23 Jan 2011CPOL11 min read
Getting value from your data historian and recovering data using ProcessBook


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.


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;



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

Dome End Factor:


Cylinder Factor:

PuttingHistorianToUse/formula_5.png ; PuttingHistorianToUse/formula_6.png



Note: a is in radians

For both Factors,


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

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:

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:

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

'24-Hour Fiscal Period Dates
Dim fiscalPeriodStartDate As Date
Dim fiscalPeriodEndDate As Date
fiscalPeriodStartDate = returnPreviousStartDate(CDate_
fiscalPeriodEndDate = returnPreviousFinishDate(CDate_
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).

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 = _
    Dim scalePeriodFinishValue As Single: scalePeriodFinishValue = _
    TextScalePeriodStartValue.Contents = scalePeriodStartValue
    TextScalePeriodStartTime.Contents = scaleValues.Item(1).TimeStamp.LocalDate
    textScalePeriodStartVolume.Contents = _
	CalculateScaleVolume(scalePeriodStartValue) * 1000
    TextScalePeriodFinishValue.Contents = scalePeriodFinishValue
    TextScalePeriodFinishTime.Contents = _
    textScalePeriodFinishVolume.Contents = _
	CalculateScaleVolume(scalePeriodFinishValue) * 1000
    textScalePeriodVolume.Contents = Val(textScalePeriodStartVolume.Contents) - _
    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 = _
    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 = _
    textCorrPeriodFinishVolume.Contents = _
	CalculateCorrossionVolume(corrPeriodFinishValue) * 1000
    textCorrPeriodVolume.Contents = Val(textCorrPeriodStartVolume.Contents) - _
    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
MsgBox "Error Calculating: " & Err.Description

End Sub

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

'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)
    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
    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:

'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

'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


    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


    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:

 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:

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:

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.


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


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


About the Author

Scotland Scotland
I have been working in the Oil & Gas Industry for over 30 years now.

Core Discipline is Instrumentation and Control Systems.

Completed Bsc Honours Degree (B29 in Computing) with the Open University in 2012.

Currently, Offshore Installation Manager in the Al Shaheen oil field, which is located off the coast of Qatar. Prior to this, 25 years of North Sea Oil & Gas experience.

Comments and Discussions

QuestionWoo hoo. Pin
Pete O'Hanlon5-Sep-11 23:33
mvePete O'Hanlon5-Sep-11 23:33 
AnswerRe: Woo hoo. Pin
DaveAuld6-Sep-11 3:28
professionalDaveAuld6-Sep-11 3:28 
GeneralMy vote of 5 Pin
JF201525-Feb-11 1:34
MemberJF201525-Feb-11 1:34 
GeneralMy vote of 5 Pin
Lyle M26-Jan-11 6:46
MemberLyle M26-Jan-11 6:46 
GeneralGreat article - I too have searched for PI resources Pin
Lyle M26-Jan-11 6:44
MemberLyle M26-Jan-11 6:44 
GeneralRe: Great article - I too have searched for PI resources Pin
DaveAuld26-Jan-11 7:37
professionalDaveAuld26-Jan-11 7:37 
GeneralMy vote of 5 Pin
Petr Pechovic26-Jan-11 2:02
professionalPetr Pechovic26-Jan-11 2:02 
GeneralMy vote of 5 Pin
Abhinav S23-Jan-11 5:24
MemberAbhinav S23-Jan-11 5:24 
GeneralRe: My vote of 5 Pin
DaveAuld23-Jan-11 5:36
professionalDaveAuld23-Jan-11 5:36 
GeneralRe: My vote of 5 Pin
Abhinav S23-Jan-11 6:45
MemberAbhinav S23-Jan-11 6:45 
GeneralMy vote of 5 Pin
Ahmad Fattahi18-Jan-11 10:11
MemberAhmad Fattahi18-Jan-11 10:11 
GeneralGreat article, but PI doesn't exactly come cheap ... Pin
Espen Harlinn15-Jan-11 4:09
mvaEspen Harlinn15-Jan-11 4:09 
GeneralRe: Great article, but PI doesn't exactly come cheap ... Pin
DaveAuld15-Jan-11 9:11
professionalDaveAuld15-Jan-11 9:11 
GeneralInteresting Article Dave Pin
Garth J Lancaster14-Jan-11 17:57
mveGarth J Lancaster14-Jan-11 17:57 
GeneralRe: Interesting Article Dave Pin
DaveAuld14-Jan-11 18:05
professionalDaveAuld14-Jan-11 18:05 
GeneralGood news: there is a resource on the web! Pin
StevePilon18-Jan-11 8:56
MemberStevePilon18-Jan-11 8:56 
GeneralRe: Good news: there is a resource on the web! Pin
DaveAuld18-Jan-11 9:23
professionalDaveAuld18-Jan-11 9:23 
GeneralRe: Good news: there is a resource on the web! Pin
StevePilon18-Jan-11 11:04
MemberStevePilon18-Jan-11 11:04 
GeneralRe: Good news: there is a resource on the web! Pin
Ahmad Fattahi18-Jan-11 9:29
MemberAhmad Fattahi18-Jan-11 9:29 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.