65.9K
CodeProject 正在变化。 阅读更多。
Home

数据历史记录器——既然买了,就用起来!真实世界案例

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.97/5 (17投票s)

2011 年 1 月 14 日

CPOL

11分钟阅读

viewsIcon

79390

从数据历史记录器中获取价值并使用 ProcessBook 恢复数据

引言

无论您从事哪个行业,都离不开数据,海量的数据,来自各种可能来源的数据。数据可以通过人工收集,即通过人工将信息写入纸张,或者将数字输入电子表格或数据采集器;也可以通过自动化系统自动收集,例如控制系统历史记录器,或者由专业的数据库存储库导入其他系统。

作为石油生产平台,我们随处可见数据,从基本的水系统压力、柴油库存、采油井测试数据,甚至当前天气状况的数据。而且大部分数据都被收集起来了。我曾多次坐下来思考,我们为什么要收集这些数据,它有什么好处?我们经常收集数据,以防将来某天可能会有用,是的,这种情况确实发生。

我们甚至花费巨额资金在专门用于收集数据的系统上,然后随着时间的推移,再想办法如何利用所有这些信息。

本文将以我最近实施的一个真实世界案例为例,说明如何利用一些基本数学和原始数据。

背景

在过去的几年里,我们一直在升级和更换我们的控制系统,升级到 DeltaV,其中包括更换执行所有过程测量和控制的现场仪表。DeltaV 系统有自己的本地数据历史记录器,用于收集过程参数并记录任何报警/事件和操作员交互。现在,由于系统位于北海中部,陆上工程团队访问数据变得非常不便。我们确实在陆上控制室部署了操作员站,但考虑到非合格人员可能意外关闭操作的风险,这些工作站的访问权限受到限制,而有很多人都想访问过程数据。

除此之外,油田的其他平台上都安装了 DeltaV 系统,还有其他独立的系统,例如燃气压缩机和基于 PLC 的控制系统,因此数据分散在整个油田。如果您再开始通过海底管道将石油和天然气、采出水和电力连接到这些平台,那么将整个油田的数据整合起来,并实时查看所有不同过程的直接交互可能会变得很困难。

解决这个问题的方法是引入一个独立的专业数据历史记录器。它能够从各种系统吸取所有所需数据并进行集中管理,然后将其提供给所有需要信息的人。这在实时控制系统和数据之间提供了一个关键的隔离/安全层。

Forties Field

图 1:福尔蒂斯油田平台布局和基础设施

言归正传——让数据历史记录器发挥作用

我们每年花费数百万美元从地下开采石油。您可以想象,任何停机时间都会立即导致收入损失。两个可能迅速影响工厂运营的主要问题是管道腐蚀和管道内部的结垢。为了管理这两个因素,化学品注入在确保产品留在管道中并能自由流动方面起着关键作用。请参阅本文中的图片和文字,了解结垢形成的速度之快,它会让你大吃一惊!它不仅会导致收入损失,而且维修成本也可能变得非常昂贵。

作为减轻腐蚀和结垢影响的一种方法,化学品被注入到过程中。我们还使用化学品进行许多其他用途,例如除氧、破乳等,但在我的装置中,主要有两种:缓蚀剂和阻垢剂。这些化学品储存在船上并自动注入。

首先,作业团队定期进行油井测试以确定产油量和产水量,陆上化学家利用这些信息来确定相关化学品的最佳注入速率。化学品价格昂贵,显然过度注入会浪费金钱,但也会对工艺产生不利影响,因此准确确定这个数字至关重要。然后将此注入速率输入控制系统,由控制系统调节化学品的流量。随着装置上库存的耗尽,新的批次将被订购并倾倒到当地平台化学品储存容器中。

由于化学品管理至关重要,我们在控制系统历史记录器和中央历史记录器中记录储存罐的液位以及注入流量。太好了,现在我们正在记录数据。我们该如何处理它呢?我们如何利用这些数据?

通过记录的注入流量,我们可以立即看到注入的化学品是否因任何原因停止,也许注入套管堵塞了,或者注入泵出现了故障。理论上,我们还可以获得注入化学品的流量总量。

陆上平台工程师走过来问:“我能否计算出在任何给定时间储存容器中有多少体积的化学品?我能否确定在过去的24小时财年期间我们使用了多少化学品?”这些信息可以用来查看注入系统的性能是否符合预期,并允许交叉核对化学品的利用率与采购量。

然后工程师郑重地递给我一些他从工艺工程师那里获得的关于标准容器体积计算的方程,并想知道这些方程是否可以以某种方式自动使用。

挠头时间——将其组合起来

根据工程数据,我们有容器尺寸、容器上液位仪表的安装位置信息以及液位变送器的测量范围限制。历史记录器中存储了测量范围的百分比。因此,利用基本数学原理,可以确定容器中储存的化学品体积。让我们来看看这个容器;

Storage Vessel

图 2:化学品储存容器

如图2所示,该容器有3个独立的储存隔间,每个隔间用于储存不同的化学品。可以看出,隔间1由一个球冠端和一个圆柱形部分组成,隔间2只是一个圆柱形部分。每个隔间中显示的百分比代表指示液位的历史值和一个代表性的条形图(好吧,我承认,比例不完全准确!)。我们拥有的且计算所需的工程尺寸数据是:

  • D = 容器内径
  • L = 圆柱段长度
  • b = 球冠段长度
  • Is = 仪表测量起始点高度
  • Ih = 仪表测量高度

让我们关注隔间1,因为它由球冠端和圆柱段两部分组成,而隔间2的计算只需用到圆柱段。

隔间1的体积可概括为:总体积 = 球冠体积 + 圆柱体积;计算中所有尺寸数据均以米 (m) 为单位,因此体积将以立方米 (m3) 为单位;

PuttingHistorianToUse/formula_1.png
其中

PuttingHistorianToUse/formula_2.png

现在,我们只对基于液位的局部体积感兴趣,为此,我们需要一些额外的方程

球冠端系数

PuttingHistorianToUse/formula_3.pngPuttingHistorianToUse/formula_4.png

圆柱系数

PuttingHistorianToUse/formula_5.png ; PuttingHistorianToUse/formula_6.png

PuttingHistorianToUse/formula_7.png;

注意:a 的单位是弧度

对于两个因子,

PuttingHistorianToUse/formula_8.png

让我们来举一个例子,以确保我们都在同一个思路上!对于隔间 1,尺寸如下:

  • 容器内径:2米
  • 圆柱长度:0.9米
  • 球冠长度:0.408米
  • 仪表起始点:0.16米
  • 仪表量程:1.84米
  • 指示液位:60% [是的,我知道草图中显示45.7%,但我们将坚持整数!]

所以,首先计算 H1 = 0.16 + (1.84 * (60/100)) = 1.264m。

圆柱体体积
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

圆柱体积 = 1/4 p D2LF(Zc) => (22 * 0.90 * 0.67 * p) / 4 => 1.89m3

圆顶体积
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

球冠端体积 = 1/6 * p * 0.204 * 23 * 0.69 = 0.59m3

总体积 = 球冠 + 圆柱 = 1.89 + 0.59 = 2.48m3

在历史记录器客户端工具中实现代码

现在我们已经有了必要的公式和处理过的数据,我们可以集中精力将其转化为有用的东西。

我们购买并实施的数据历史记录器是 OSISoft PI 系统,本示例中使用的客户端工具称为 ProcessBook。在使用这个工具时,我注意到网上可用的信息非常少。通常用户群会涌现出来,大家会分享代码片段等,但对于这些产品来说,很难找到任何东西。这也是我希望在这里分享这个例子的另一个原因,也许可以帮助更多的用户社区进行分享。

ProcessBook可以使用Visual Basic for Applications进行编程。ProcessBook安装时,会随同安装PISystem SDK,其中包含访问PI历史数据库中存储数据所需的所有库和服务。用户将配置与任意数量的PI服务器的连接,并根据需要使用其安全凭据登录,这将允许他们访问其授权的数据集。

创建了一个新的 ProcessBook 显示,其中包含显示实时数据、显示文本和按钮的图形。当用户点击按钮时,VBA 代码会触发,获取用于当前快照体积的当前液位指示。它还会确定上一个财年 24 小时周期,然后获取历史液位信息,并使用该信息确定在该周期内使用的化学品总量。

上面使用的方程式被分解成函数,可以根据需要在代码中调用。

当用户点击按钮时,它会触发函数 DoCalcs()

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

需要定义一些 PI System 特有的对象来从历史服务器中获取数据,包括服务器对象和两个数据点对象

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")

接下来,我们获取当前快照的实时值,然后可以用它来确定隔间中当前快照的体积

'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

代码的下一部分确定了上一个财务期24小时周期。在我们的安装中,我们有一个从18:00到18:00的报告财务期,即一天从晚上6点开始,持续一整夜直到第二天晚上6点。

'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

从历史记录器中抓取财务期内的记录数据值,并使用起始值和结束值来计算该财务期内使用的体积。如果该时间点没有精确的读数,历史记录器将对数据进行插值,并提供起始时间和结束时间的值。然后,它会用已有的信息以及该期间找到的数据值数量更新用户显示。它会为两个隔间(结垢和腐蚀)重复此操作。

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

用于计算财务周期开始和结束日期/时间的两个辅助函数

'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

然后我们有两个计算函数,一个用于`Scale`(带圆顶端),一个用于`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
'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

下面的函数用于确定球冠端局部体积的因子,并以液位高度和容器直径作为输入值

 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

以下函数用于确定圆柱体中部分体积的因子,并以液位高度和容器直径作为输入值

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

下面这个函数用于计算 a,并以液位高度和容器直径作为输入值

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   

我们最终得到的结果

因此,当用户点击按钮并运行代码时,图形组件会更新所需的信息。下面是我实现的显示屏的快照,代码还将立方米转换为升,因为这有助于将注入速率与使用速率关联起来。

ProcessBook Display

图 3:ProcessBook 显示实现

所以,你看,这就是如何利用历史数据来帮助管理你的化学品策略,并附带了一些代码,如果你正在使用企业级 OSISoft PI Historian 和 ProcessBook 客户端工具,如何获取这些数据。

兴趣点

Google Chart API 被用于创建公式图像,详情请参阅此处

历史

  • 2011 年 1 月 23 日 - 将文本公式替换为图片 
  • 2011 年 1 月 15 日 - 首篇文章发布
© . All rights reserved.