1. Equipment Is Not Static

Everything changes. Improvements and upgrades are made. Repairs are made. When these changes result in changes to the PI Databases, existing applications may no longer work correctly. A simple example would be replacing a control loop--perhaps a new multi-loop controller from a different manufacturer. This change would require a new interface and a new set of points. To handle changes, modules must be able to historize changes. Historization of modules is handled through the version interface.

Historizing modules is similar to historizing values in the PI Archive. Where PI Archive values have a timestamp, each module value has an effective date. A PI Archive value is usually a numeric or string and the timestamp represents the time of the event; the module value effective date represents the date a particular module value became effective.

A module value is considered "in effect" up to the next newer module value. If there are no newer modules, the module value is in effect up to current time.

We introduced a new concept here--"module value." An instance of a module is actually a specific module value. To accommodate module historization, a module is actually and array of module values sorted by effective date.

All module changes do not require a new value. Historizing minor changes, such as changing a description, would use excessive resources. Therefore, module changes are not automatically historized; all changes increment the version revision number and record who and when the modification was made. This is the default behavior of module modifications. Historizing changes, which actually creates a new value with a new effective date, requires special actions. These actions are provided by the version interface. The version interface is accessed by the module's Version property.

The following graph is a time-line of changes to a module. The horizontal axis shows revisions--changes to the module that are not historized, the vertical axis shows historized changes.


Following the above timeline, the module "tic-104" was created on October 11, 2000. The module was created to access PIPoints that have existed since the PI System was installed, therefore, the default effective date, January 1, 1970, is used. The revision number of the module is incremented with each modification. Therefore adding a few PIProperties and PIAliases increments the revision number several times. Block 1C represents the module after all initial edits are complete. 

Sometime during the week of November 20th, 2000, the physical controller was upgraded. The upgrade involved adding a new PI Interface and appropriate PI Points. The PI Points currently referenced by tic-104 are no longer valid--the controller and interface supplying data to the points has been removed. Of course, the historized data for these points are still valid. We have a situation where prior to November 20th; our application must reference a different set of points than now. To accommodate this change, we create a new value of tic-104. This is presented by block 2A. 

Several days after updating the controller and interface, we get around to creating the new tic-104 value. The value is created on November 30th, but since the actual controller replacement was performed the prior week, the effective date is set to November 22nd. A new module value starts with a revision number of 1. The previous value can still be edited, and its revision number will be incremented; therefore, a revision number only makes sense within the context of a particular value. Block 2B represents the module value after creation and initial edits.

On December 2nd, the module value is edited one more time. The original effective date was set to November 22nd, but the new controller installation was actually completed on November 20th. The effective date was edited to reflect this. Blocks 3A and 3B reflect continued evolution of the module. In this case the single loop controllers are replaced with DCS based control.

  1. Revisions Versus Historization

It is important to stress the differences between revisions and historization. Historization results in creation of a new value on the server. Obviously there is overhead associated with this--disk, memory, and module retrieval time are affected. Modules should be historized when the changes are significant. A module should be revised for minor changes, such as configuration errors.

  1. Important Hierarchy and Historization Concepts

Here are some important concepts to understand about how the hierarchy and historization of the PI Module Database works.

A PIModule object has a dual nature.

A PIModule object represents both an individual value (i.e., it has an effective date) AND the whole time series of values for that PIModule. You can navigate to other values of the PIModule using the PIModule.Version interface.

This is different from the Point Database. For that you have a PIPoint object and a separate collection of PIValues for the time series data of that PIPoint.

What is a PIModule value?

Each value of a PIModule records the PIProperties, PIAliases, and submodules of the module that were in effect at the EffectiveDate of the value.

The Name, Description, PIHeading, and UniqueID are the same for all values of a PIModule.

I'm confused about how to use the history feature of the PIModule Database.

Concentrate on two things:

  1. Think about what a PIModule represents and record the changes in its state (PIProperties and PIAliases) using values with the EffectiveDate property defined for when those changes took place.
  2. Think about what submodules a PIModule has at any particular time. If it gets a new submodule or loses a submodule, create a new value with the different module references in the PIModules collection.

A refinement of 2 is that you don't have to create a new value of a PIModule if a new submodule is just coming into existence (e.g., a new tank was built in a tank farm). Because the submodule's effective date is later than that of the PIModule value, the submodule won't show up in the PIModule's PIModules collection until the query date is on or after the effective date of the submodule's first value.

If you do these two things, the historization of your hierarchy should work the way you want. You should be able to set your query date and get the proper PIModule's as you drill down or through the PIModule.Version.Value method.

Module history is not a substitute for Tag history

Module History is not a substitute for Tag history. If the parameters of a module are changing more often than once every few months, they should be tags and put in the Module as Aliases.

What is the ObsoleteDate of a PIModule value used for?

The ObsoleteDate of a PIModule value can be used when the thing that the PIModule represents was taken out of service.

For example if a tank was demolished in the tank farm, you could set the ObsoleteDate of the last value of the PIModule for the tank to the date it was taken out of service. This is will preserve the tank PIModule for applications that are looking at historical data when the tank was in use.

The ObsoleteDate is just an attribute of each value. It is not used by the SDK or the PI Server to determine whether or not to return a value when asking for the PIModules collection of a parent module.

It is up to each application to decide how to make use of the ObsoleteDate.

Parent modules don't have references to specific values of submodules.

When you insert a submodule into the PIModules collection of a PIModule, you aren't inserting a reference to a particular value of the submodule. Instead you are inserting a reference to the whole time series of values for that submodule. This is particularly confusing for many users since when you do the insertion you have a particular value of the submodule. Remember, a PIModule object represents both a particular value and the whole time series of values.

What happens when I set the query date and ask for the PIModules collection of a PIModule?

When you set the query date of a PIModule and access the PIModules collection, here is what actually happens on the server:

The value at or before the query date of the PIModule is retrieved on the server. It doesn't matter which value of the PIModule you have in your program.

The collection of submodule references for that value is retrieved. You can get this collection using the PIModule.Version.ChildCollection method.

The value of each submodule at or before the query date is retrieved. It is this collection of values of the submodules that is returned. If there is no value for a submodule at or before the query date, that submodule is not included in the collection.

a) If you set the query date before the EffectiveDate of the first value of the parent in the PIModuleDB, the PIModules collection will always be empty.

b) You can set the query date of the PIModuleDB object to get PIModule values in the root PIModules collection at or before the query date.

What is the default value for the query date?

The default value for the query date for a PIModule (or the PIModuleDB) is the current time. If you don't specifically set the query date of a parent module, each time you access the PIModules collection, the current server time is used in the procedure in the previous concept (above). This means you will get the current value of all submodules by default.

What should I set the query date to?

Once you have set the query date of a PIModule, the values of the submodules you get from the PIModules collection all have that same query date set. This allows you to start at a root module (or the PIModuleDB) and drill down through PIModules collections without having to set the query date each time. The idea is that you set the query date to a time context that is pertinent to your application (say Batch Start Time or End time) and then get the values of the modules you want for that time context.

What is the PIModule.Version.ChildCollection method for?

To get the actual submodule references for a particular value of a PIModule, use the PIModule.Version.ChildCollection method. This method ignores the query date.

This is used for applications that need to maintain the historized hierarchy.

What should I use for an EffectiveDate?

The effective date represents the date the value of the PIModule went into effect. It has nothing to do with when a user added the PIModule to the PIModule DB. That is recorded in the CreationDate. Edits are recorded in the ModifyDate.

The default value for the EffectiveDate when adding a PIModule is UTC "1". This is considered the "Beginning of Time" and is 1 second after midnight on Jan 1, 1970 UTC time. In the absence of other information about when what a PIModule represents (a pump for example) actually came into existence, the default value is a good choice for an effective date.

Later as new PIModule values are added, the effective date should reflect when that change went into effect. Many users want to set the effective date to current time, which doesn't make sense.

Where is the primary location of a PIModule in the hierarchy?

There is NO preferred location in the hierarchy for a particular PIModule. It can appear as many times as makes sense. This is different than the directory/file tree of Windows for example. In that tree, the file only exists in one location. You can put shortcuts to it in other locations.

For the Module Database, all locations are short cuts to a single entry in a table of all PIModules. This table only exists on the server and is not available in the SDK. The only limitation is that we prevent circular references: a module can't have itself as a parent.

A second difference between the PIModule hierarchy and a directory/file tree is that a PIModule represents both the directory (through its PIModules collection) and the file.

A third difference is that the PIModule hierarchy can change over time and you can programmatically retrieve the hierarchy at any time in history.

I'm having trouble deleting a PIModule because it has submodules.

You can't delete a PIModule if ANY of its values have submodules. Remember that each value of a PIModule can have different submodule references. The PI Module Database doesn't allow orphaned modules.

You need to use the PIModule.Version.ChildCollection method to get the actual submodule references of a particular value of a parent PIModule. You can't use the query date. Removing a submodule from this collection removes that reference on the server.

Iterate through each value of the parent and remove the submodule references. If this is the last place in the hierarchy that a submodule is located, you'll have to make sure all of its values don't have submodules either.

Here is a Visual Basic subroutine that properly deletes all the submodule references of a PIModule so that it may be deleted from the PIModules collection it is in.

Private Sub DeleteModuleChildren(module As PIModule)


' Deletes all children of the passed module. If necessary, deletes the children of the children, etc. too.

' This is necessary when the passed module has the last database reference to the child.


Dim children As PIModules

Dim child As PIModule

Dim nbrItem As Long

Dim versModule As IPIVersion

Dim ptQD As New PITime

Dim seaModule As PIModule


' Start with the current version of the module


Set seaModule = module.version.CurrentValue


' Get the child collection from the IPIVersion interface and delete those references. If this is the

' last reference for a child and that child has children, recursively call this subroutine.



Set children = seaModule.version.childCollection

For Each child In children

On Error Resume Next ' we are going to check for specific errors

children.Remove child ' remove each child from this version's collection

If (Err.Number = pseSERVERDBREMOVEFAIL) Then


' This must be the last reference for this child and it has children.

' Verify the error we got and then clear it


On Error GoTo 0

Err.Clear ' previous line clears error, but just to make sure

DeleteModuleChildren child ' Delete this module's children


' At this point we should be able to delete this child


children.Remove child ' an error at this point will jump out of routine

ElseIf (0 <> Err.Number) Then ' quit on any other error

Err.Raise Err.Number, Err.Source, Err.description, Err.HelpFile, Err.HelpContext ' pass this error through

End If

On Error GoTo 0 ' turn errors back on



' All children of this version deleted, get previous value of module


On Error Resume Next ' we are going to check for specific errors

Set seaModule = seaModule.version.PrevValue ' get previous value, if this fails, quit

If (Err.Number = psePIVERSIONPREVVALUE) Then

On Error GoTo 0 ' no more versions, turn error checking back on

Err.Clear ' previous line clears error, but just to make sure

Exit Sub ' quit

ElseIf (0 <> Err.Number) Then ' quit on any other error

Err.Raise Err.Number, Err.Source, Err.description, Err.HelpFile, Err.HelpContext ' pass this error through

End If

On Error GoTo 0 ' turn errors back on

Loop While True

End Sub

  1. Example

Here's a VB example showing creation of a new module value:

Private Sub cmdNewValue_Click()
   Dim vbDate As Date
   Dim EffectiveDate As New PITime
   Dim VersionInterface As IPIVersion
   Dim TIC104 As PIModule
   Dim TIC104Improved As PIModule
   Dim pts(7) As PIPoint
   Dim MfgData As PIProperty
   Dim InstallationData As PIProperty
   Dim Prop As PIProperty
   Set Srv = PISDK.Servers.DefaultServer
   Srv.Open ("uid=piadmin")
   Set Controllers = Srv.PIModuleDB.PIModules.Item("Controllers")
   Set TIC104 = Controllers.PIModules.Item("tic-104") ' Module where we're creating a new value
   Set VersionInterface = TIC104.Version ' The version interface supplies the methods for creating a new value
   vbDate = #11/22/2000#
   EffectiveDate.LocalDate = vbDate ' Effective date of new value
   ' Use the copy method, this adds a new value to the module, identical to the previous value
   ' but with the new effective date. This is a good starting point for editing the value
   ' with the appropriate changes.
   Set TIC104Improved = VersionInterface.Copy(EffectiveDate)
   ' Edit the aliases and properties of this value to reflect the new controller
   ' Create refrences to the PI Points
   Set pts(1) = Srv.PIPoints.Item("tic-104Improved.pv")
   Set pts(2) = Srv.PIPoints.Item("tic-104Improved.sp")
   Set pts(3) = Srv.PIPoints.Item("tic-104Improved.o")
   Set pts(4) = Srv.PIPoints.Item("tic-104Improved.i")
   Set pts(5) = Srv.PIPoints.Item("tic-104Improved.p")
   Set pts(6) = Srv.PIPoints.Item("tic-104Improved.d")
   Set pts(7) = Srv.PIPoints.Item("tic-104Improved.m")
   'Edit the aliases--this only edits this value, the previous value is unaffected
   Set TIC104Improved.PIAliases.Item("ProcessVariable").SetDatasource(pts(1))
   Set TIC104Improved.PIAliases.Item("SetPoint").SetDatasource(pts(2))
   Set TIC104Improved.PIAliases.Item("Output").SetDatasource(pts(3))
   Set TIC104Improved.PIAliases.Item("Integral").SetDatasource(pts(4))
   Set TIC104Improved.PIAliases.Item("Proportion").SetDatasource(pts(5))
   Set TIC104Improved.PIAliases.Item("Derivative").SetDatasource(pts(6))
   Set TIC104Improved.PIAliases.Item("Mode").SetDatasource(pts(7))
   'Edit the PIProperties--the previous value is unaffected
   Set MfgData = TIC104Improved.PIProperties.Item("Manufacturer Data")
   Set InstallationData = TIC104Improved.PIProperties.Item("Installation Data")
   MfgData.PIProperties.Item("Name").Value = "Improved Controller, Inc."
   MfgData.PIProperties.Item("Model").Value = "IMP-23456"
   MfgData.PIProperties.Item("Serial Number").Value = "454353489"
   InstallationData.PIProperties.Item("Technician").Value = "Jack Frost"
   InstallationData.PIProperties.Item("Comments").Value = "No problem."
End Sub

Here's an example of an effective date edit.

Private Sub cmdEditEffectiveDate_Click()
   Dim EffectiveDate As New PITime
   Dim TIC104 As PIModule
   Set Srv = PISDK.Servers.DefaultServer
   Srv.Open ("uid=piadmin")
   Set Controllers = Srv.PIModuleDB.PIModules.Item("Controllers")
   Set TIC104 = Controllers.PIModules.Item("tic-104") ' Module where we're creating a new value
   EffectiveDate.LocalDate = #11/30/2000 5:00:00 PM#
   TIC104.Version.EffectiveDate = EffectiveDate
End Sub

No changes are required for our PI ProcessBook example to pick up the new configuration:

  1. Query Date

A module is really made up of an array of one or more module values. This requires a technique to insure the desired value is returned to an application. The QueryDate property supplies this functionality. QueryDate always defaults to current time, therefore, by default, the most recent module value is returned.

This is proper behavior for any application that deals with current time. For example, most PI ProcessBook displays have a default end time of current--the ubiquitous "*" PI time string. Applications that allow looking back in time, will need to set the QueryDate appropriately.

We'll improve our PI ProcessBook example to take advantage of the module values. The example, as developed to now, will work correctly on load. This is because the trend end time defaults to current time and the default module database behavior returns the current module value. But if the trend time range is changed, we could trend the wrong PI Points. Here's what happens if we set the time range back before new controller was installed:


To fix this problem, we'll create a time range change handler for the trend object. The handler will use the new trend start time as the QueryDate. Then re-load the controller module. This will get the module value that was in effect at the start time of the trend. Every module has a QueryDate property. Access to the PIModules collection uses the QueryDate set in the parent module. Therefore, in our example, to get the correct controller value we will set the QueryDate property of the Unit that owns the controller to the trend start time. Setting the QueryDate property of a module does not change that module value; this property only affects access to the PIModules collection of the module.

Private Sub Trend_TimeRangeChange(ByVal StartTime As String, ByVal EndTime As String)
   Dim QueryDate As New PITime
   ' on load it's always current time, so no need to do this.
   ' Also, modifying the trend Raises TimeRangeChange, therefore set a flag to avoid re-entrancy
   If g_bLoading = False And g_bNoReEntrancy = False Then
      g_bNoReEntrancy = True
      QueryDate.LocalDate = StartTime
      Dim strCurrentController As String
      ' Set the query date of "parent" to the start of the trend. Then get the controller again
      ' this will get the controller that was "in effect" at the start of the trend.
      If Not Unit Is Nothing Then
         Set Unit.QueryDate = QueryDate
         strCurrentController = Controller.Name
         Set Controller = Unit.PIModules.Item(strCurrentController)
         Trend.SetTimeRange StartTime, EndTime
      End If
      g_bNoReEntrancy = False
   End If
End Sub

Here's our application when we set our time back to November 19th. Notice the correct PI Points are shown in the display. Also the installation and manufacturer data is that of the module value that was in effect on November 19th.



The QueryDate property is not the only technique for accessing values of a module. There are several methods to retrieve or navigate through the values. We will upgrade the PI ProcessBook example to demonstrate some of these features; the Version Object Reference has details on all the methods and properties.

It is possible to specify a time range that crosses effective date boundaries. The application should at least be aware of this issue. There are a few things we could do when this case is encountered. We could adjust the time range to avoid this situation, or just warn the user. We will use the version method "NextValue" to navigate to the next more recent value and compare that value's effective date with the trend end time. If the trend end time is newer than that effective date, we will write a warning message to the PI ProcessBook screen. We will also use the version interface to add some version information data to the process book screen. Here's the code, and resulting display:

Private Sub ShowVersionInfo()
   Dim strWarning As String
   Dim strVersionInfo
   Dim NextValue As PIModule
   Dim tsTemp As PITime
   Dim etTrend As New PITime
   strVersionInfo = ""
   If Not Controller Is Nothing Then
      On Error Resume Next ' Turn off error handling, if this is the last value, there won't be a next
      Set NextValue = Controller.version.NextValue
      On Error GoTo 0
      If NextValue Is Nothing Then
         strWarning = ""
         etTrend.LocalDate = Trend.EndTime
         If etTrend.UTCSeconds NextValue.version.EffectiveDate.UTCSeconds Then
            strWarning = "Warning: time range end is newer than next value effective date: " _
               & NextValue.version.EffectiveDate.LocalDate
         End If
      End If
   strVersionInfo = "Module revision " & Controller.version.RevisionNumber & " " & strWarning
   End If
   lblVersion.Caption = strVersionInfo
End Sub

Enabling Operational Intelligence