Skip navigation
All Places > All Things PI - Ask, Discuss, Connect > PI Square中文论坛 > Blog
1 2 Previous Next

PI Square中文论坛

23 posts
kenie123

JDBC 的连接URL 的写法

Posted by kenie123 Dec 26, 2017

PIJDBC Driver 所使用的URL (分别为采用 PI OLEDB Enterprise 或者  PI OLEDB Provider 来实现连接

 

而 连接的认证方式

 

 

insert into piarchive..picomp2(tag,time,value) values('ppnie_test','*',101);

 

[PIOLEDB] Row insertion failed. [PI SDK] Insufficient permission to access or complete operation.[-10401] No Write Access - Secure Object

功能描述:

     PI Server 在 服务器A 上。 PISQLDAS 和 PI JDBC Driver 在服务器B上。服务器A 与 服务器B 的时间,有误差。当查询 当前最新记录时,以 服务器A 为准。

 

 

 

相关资料:

 

原谅我英文水平有限,这段话翻译过来:

在此版本中,PISQLDAS计算机和PIJDBC驱动程序客户端计算机的时区/DST设置必须相同。

此外,无法使用PIOLEDB-特定连接属性时区。如果指定,该设置没有效果。

 

换一句话说:PIJDBC Driver 所在服务器与 PISQLDAS 所在的时区必须相同,不能通过Time Zone 来设置。如果 是同一个电脑上,则无需处理。

PIJDBC Driver 默认采用的 Time Zone 为 local

那么 在这里我要 实现 功能描述中的问题,是否是 只能考虑 设置 PISQLDAS 或者  PI OLEDB Enterprise 或者  PI OLEDB Provider  等的 Time Zone 呢?

 

解决方案:

     暂未找到合适的方法。我不知道设置在哪里!!!!

(0)前言

         在官方上多次查询,以及官方文档的多次阅读,我表示依旧还是有很多不懂的地方。本方案是我目前暂时采用的方案,并不完善,还需改进。请 其他大神路过的话,指出我的方案中的不足之处。

(1) 背景

          我们现有的服务器已经安装了 PI Server。目前正在开发的应用程序需要访问PI Server 中的数据,以方便显示图表或者更新数据。应用程序采用Java 开发。

(2) 定方案

     由官网(Developer Technologies)介绍可知:常用的访问PI数据库的方案有

    • 使用 JDBC Driver 或 ODBC Driver
    • 使用 PI Web Api
    • 使用 sdk

          为了适用于Java 程序的开发,采用 JDBC Driver 方案来实现 PI 数据库的访问。这样可以使用数据库连接池的方式来实现多数据源方案实现数据库连接。

(3) 原理

PI的JDBC驱动程序是一个java数据库连接驱动,通过SQL查询提供了强大的数据访问PI系统 。PI JDBC Driver 提供了一个类似于java 访问Mysql或者Oracle 同样的方式。

但是,JDBC Driver 是 通过中间层 PI SQL DATA Access Server 来实现 PI JDBC driver 和 PI OLEDB Enterprise/PI OLEDB providers 之间的交互的。
PI SQL DAS(OLE DB)在客户端 通过 Net.TCP 或者 HTTPS 来提供安全的网络连接
JDBC
安装架构模式1
架构模式2

(4) 环境准备

          - PI JDBC Dirver (1.5.17051.1)

          - PI SQL DAS (1.5.16302.2)

          在官网上下载了对应的安装包。

         - 已安装 Java JRE 1.6 以上版本。设置了环境变量 JAVA_HOME

         - 64 位 windows 操作系统

 

(5)安装步骤

1)安装 PI OLEDB Provider 或者 PI OLEDB Enterprise

  • PI JDBC Driver 2016 在 windows 上基本要求
    • windows7 及以上
    • JRE 7 及以上
    • 需要安装 Microsoft .Net Framework 4.6 (企业版中包含此安装包)
  • 安装步骤
    • 下载 PI OLEDB Provider 或者 PI OLEDB Enterprise
    • 根据安装向导运行,运行 PI-OLEDB_*.exe,全部默认
    • 安装完成
    • 注意
      • PI OLEDB Enterprise 比 PI OLEDB Provider 要全面较多。所安装的软件也会更多。
      • msiexec.exe /i PIOLEDBEnterprise64.msi REBOOT=Suppress ALLUSES=1 /qn

2)安装PI SQL DATA Access Serve(OLE DB)

  • PI SQL DAS 在 windows 上基本要求
    • 需要安装 Microsoft .Net Framework 4.6
    • 需要安装 PI OLEDB Provider 或者 PI OLEDB Enterprise
  • 安装步骤
    • 下载 PI SQL Data Access Server (OLE DB) Install Kit
    • 使用拥有管理员权限的账户来安装
    • 根据安装向导运行,运行 PISQLDAS_*.exe, 点击Ok
      • 当你运行安装时,PI SQL DAS 会根据你的操作系统版本自动选择安装 32bit 或者 64bit
      • HTTPS port 默认为 5461,Net.TCP 为 5462
      • 自动安装对应的证书
      • 会修改对应端口的防火墙
    • 安装完成
    • 校验服务是否在运行。我的电脑->管理->服务 中查看 是否新增了一个 PI SQL Access Server(OLE DB)的服务在运行状态
      安装后服务启动

3)安装PI JDBC Driver 2016(补:现在已经出了新版2017,可以用 .net 4.6 了)

  • PI JDBC Driver 2016 在 windows 上基本要求
    • windows7 及以上
    • JRE 7 及以上
    • 已经安装了 PI SQL DATA Access Server
  • 安装步骤
    • 下载 PI JDBC Driver 2016 SP1 Install Kit (Windows)
    • 根据安装向导运行,运行 PI-JDBC_2016-SP1_.exe,全部默认
    • 安装以后,其安装目录(PIHOME),32bit:C:\Program Files (x86)\PIPC\JDBC,64 bit :C:\Program Files\PIPC\JDBC
    • 系统环境变量会自动添加或更新
      • PI_RDSA_LIB=PIHOME\JDBC\RDSAWrapper.dll
      • PI_RDSA_LIB64=PIHOME\JDBC\RDSAWrapper64.dll
      • CLASSPATH=PIHOME\JDBC\PIJDBCDriver.jar
      • 打开 cmd,运行set piset classpath 即可查询环境变量是否设置
    • 安装完成
  • 注意事项
    • 如果没有安装 PI SQL DATA Access Server,安装了 JDBC Driver 也没有什么用。你会发现 JDBC 的安装目录下提供了一个类似于 JDBC访问Mysql的包而已。
    • 甚至,你自身的系统为64bit,它却会静默安装 将32 bit 的安装上去。
    • 程序还提供了一种静默安装方案
      • msiexec.exe /i PIJDBC.msi REBOOT=Suppress ALLUSES=1 /qn
      • msiexec.exe /i PIJDBC64.msi REBOOT=Suppress ALLUSES=1 /qn

通过上述的安装步骤之后,从开始菜单打开SQL Commander Lite,可以连接上 PI 服务,并且使用 类似 sql 语句去查询PI 中的数据。具体 sql语句 查看 帮助文档如PI-OLEDB-Enterprise-2016-R2-User-Guide.pdf

 

(6)安装过程中的产生的问题

  • 从官网上面下载的包,都是分散的,不能一次完成我所想要的功能。而且,安装一个版本,其附带的准备环境不统一,造成我安装一次,则安装了很多的不同版本的环境,严重造成了资源浪费的感觉。在我安装的过程中,我装了JDBC Driver 才知道,我需要安装  PI SQL DATA Access Server 。去下载了一个,再次安装,才知道我缺少  PI OLEDB Provider 或者 PI OLEDB Enterprise 。而且安装的时候,默认会将 32位和 64 位的都安装。
    • 建议:自己精简软件包,将需要的软件包,打包成自己想要的样子。可以修改 setup.ini 来实现

 

Java 访问PI 数据库:(1)安装必要软件 - CSDN博客

希望大家踊跃完善和灌水

中国区PI System的用户经常会在第一次注册SSO (Single Sign-On) 账号的时候遇到各种各样的问题,为了帮助大家更快速地解决注册问题,我们的工程师整理了账户注册的完整流程,以及如何解决在注册中遇到的常见问题。

 

本手册包含以下主要内容:

  1. 什么是OSIsoft SSO账户?
  2. 如何注册SSO账户?
  3. 连接SSO账户到公司站点(Site
  4. 忘记SSO账户密码
  5. TSA协议的签订
  6. 常见问题及资源
  7. 附录A 个人邮箱授权书模板
  8. Revision History

 

1. 什么是OSIsoft SSO账户?

  • SSO账户链接到公司站点后,在SRP有效期内,可以访问的网站资源列表:
    • 技术支持公告
    • 产品发布公告
    • 知识库文章
    • 已知问题库
    • 产品功能改进申请
    • 文档
    • 白皮书
    • 软件
    • 实用工具

.............

 

其它具体章节请从附件的链接下载,获取完整文档。

 

感谢撰写本文档的Mia, Wenwen

最近经常有同学询问如何开发Coresight的Symbol,以及开发资源等等。先开一个贴子统一回复一下:

 

OSIsoft官方的Coresight开发站点和示例集合:GitHub - osisoft/PI-Coresight-Custom-Symbols

 

开发环境与平台要求:

1、PI Coresight 2016 或更新版本

2、熟悉JavaScript,最好对AngularJS有所了解

 

想要了解大概开发流程的同学可以参考附件的文档。

PI AF 简介在线研讨会!(2017年1017日,星期二 上午10-11点)

 

PI Vision 2017 在线研讨会 (2017516日,星期二 上午10-11点)

 

感谢您参加最新PI Integrator for Business Analytics (2016118日,星期二,上午10-11)

  • 会议录像:点此下载或是回看
  • PPT点此下载

江苏利电能源集团PI 系统升级成功案例分享在线研讨会 (2016921日,周三,上午10-11)

  • 会议录像:点此回看或是下载
  • 江苏利电能源集团吴朦:点此下载PPT
  • 北京中瑞泰科技有限公司黄咏:点此下载PPT
  • OSIsoft 张慧:点此下载PPT
  • iEM参数异动演示:点此观看演示

 

PI Coresight 2016 在线研讨会 链接(2016 年719日,星期二, 上午10-11):

  • 会议录像:点此回看或是下载
  • 徐初 PI Coresight 2016 介绍》:点此下载PPT
  • 张新桥《PI Coresight 应用初探》:点此下载PPT

站内链接:“PI Coresight 2016 ”在线研讨会(会议资料)

 

“如何让PI AF为您所用”在线研讨会   2016614日,星期二 时间:上午10 - 11

  • 会议录像:点此回看或是下载
  • 王普乐 《如何让PI AF 为您所用》:点此下载PPT
  • 孙玉鹏 《蓝星PI AF应用案例分享》:点此下载PPT
  • 马天明 《南通星辰PI AF应用案例分享》:点此下载PPT

站内链接: “如何让 PI AF 为您所用”在线研讨会(会议资料)

 

“PI Coresight 功能介绍”在线研讨会链接   (2016年3月8日,星期二,上午10点 - 11点):

站内链接: “PI Coresight 功能介绍”在线研讨会(会议资料)

 

PI Integrator for SAP HANA PI Integrator for Business Analytics20151216日,星期三,上午10点 – 11链接:

站内链接:“PI Integrator for SAP HANA 和 PI Integrator for Business Analytics”  在线研讨会(会议资料)

 

PI 2015 Future Data 会议链接为(2015年10月20日, 2015星期二,上午10点 - 11点):

站内链接:“PI System新功能介绍” 在线研讨会(会议资料)

“PI Integrator for SAP HANA 和 PI Integrator for Business Analytics”  在线研讨会已经圆满结束,我们希望您能觉得参加这次交流会是一次收获丰富的愉快经历,能够帮助你更好的了解这两个产品。

 

您可通过以下链接下载本次会议的资料:

 

 

如果您有任何问题,或是想进一步的咨询有关 PI Integrator 的产品信息,请在本帖留言或者通过以下电子邮件与我们保持联系 marketingcn@osisoft.com 。我们将为您做进一步的安排。

接上一篇有关AF analytics的最佳实践 - Part1

 

如何实现含有过滤条件的平均值(Average)、总值(Total)和汇总(Rollup)计算

关于这个内容有一篇KB文章 KB01120

下面这个例子中我们想要计算流量大于0(FlowRate > 0)时的流量的平均值(事件加权以及事件加权),仅靠TagAvg()和TagMean()函数不能实现这个功能。

m1.jpg

m2.jpgm3.jpg

要获得正确的时间加权平均,我们需要创建另外一个属性,比如叫做Filtered Flowrate,然后使用Formula 数据引用类型如下图所示:

m4.jpg

时间加权平均值的计算:使用TimeGT()函数来计算Flowrate属性在过去一天中大于0的时间。这个结果的单位是秒(s),因此我们还需要把它转化成为天(d),即PumpRunningInDays变量,接着TagTot()函数来计算Filtered Flowrate属性过去一天的总值。将两者得到的结果相除就得到Filtered Time weighted Average了。

m5.jpg

m6.jpg

事件加权平均值的计算:我们需要创建一个新的属性,在FlowRate > 0时,显示原来的值,当flowRate = 0时,显示NoOutput()

m7.jpg

接着对FilteredFlow这个属性使用TagMean()函数即可计算经过过滤以后的事件平均

m9.jpg

 

汇总计算:

同理,通过创建Filtered Production这样的属性可以对production属性的值进行过滤,并将这个值用在Rollup的计算中,即可得到过滤后的rollup的值

n1.jpg

n2.jpg

n3.jpg

n4.jpg

 

高级回填选项:

写入PI的时间戳可以是触发事件的未来时间

o1.jpg

 

如何在表达式中使用时间

这个例子中我们想要计算Volume1中的变化率,以及变化的持续时间

p1.jpg

其中st和et记录了Volume1属性的开始时间和结束时间,DateTime变量是两者之间的时间差,格式是dd:hh:mm:ss。这个结果用在ROC的计算中,并得到一个结果。DateTime变量已经被转换为秒(s),这样ROC的单位是(bbl/sec)。但当您想把这个结果映射到属性是会有下面这个错误

p2.jpg

但是,Raw Delta Time的数据类型是DateTime,没办法定义UOMs。

这里的trick是可以使用Float()函数,使用Float()可以将Delta Time变量改为double。接着可以使用Convert()函数给DeltaTimeInHours变量添加单位,然后进行rate of change(ROC)的计算。

p3.jpg

 

根据需求计算(结果不写入PI点)

AF Client (AFSDK)2.7开始,AF中多了一种新的数据引用类型,Analysis,该引用会记录表达式,但只有在客户端query的时候才会计算结果

q1.jpg

对于这种数据引用类型有如下一些内容需要考虑:

  • 计算是在AF客户端进行的
  • 计算频率是不是过于频繁?
  • 这个按需求计算是不是依赖于别的其他的按需求计算?
  • 在一个数据请求中有多少这样的计算?
  • 数据请求的间隔是多少?

以上这些因素都是影响性能的,所以这种数据引用类型应该被谨慎地使用

 

被限制使用的函数——这些函数可以被使用,但不支持summary data calls:

  • EventCount()
  • PctGood()
  • Range()
  • StDev()
  • TagAvg()
  • TagMax()
  • TagMean()
  • TagMin()
  • TagTot()

在下面这个例子中,我们计算Flow属性的5分钟平均值,并将结果映射到属性average flow。这个结果没有被映射到一个PI点,但我们仍然可以在一段时间内计算他们的值。请见下图:

 

 

q2.jpg

q3.jpg

q4.jpg

但如果您想将这个属性用于另外一个表达式的时候会得到下面这个报错,因为这个结果不能再被用于新的summary call函数中

q5.jpg

如果我们创建一个变量AvgFlowHist将计算结果映射到一个PI点中,AvgFlowHist可以被用于计算tagtot

q8.jpg

 

下列函数是不能在按需求计算中使用的,因为这些函数计算时比较消耗资源:

  • DeltaValue()
  • FindEq()
  • FindGE()
  • FindGT()
  • FindLE()
  • FindLT()
  • FindNE()
  • HasValueChanged()
  • NumOfChanges()
  • TimeEq()
  • TimeGE()
  • TimeGT()
  • TimeLE()
  • TimeLT()
  • TimeNE()
  • NoOutput()

q9.jpg

最近在pisquare上有同事发了有关AF Analytics的最佳实践的系列文章,以及一个文档(请见附件),全文比较长,下面挑选几个我认为比较重要或者新的内容介绍一下。

原文连接:

Tips and Tricks for Asset Based Analytics

Creating Analytics in Element Templates

Using Categories in Analytics

Make AF Analytics more readable - Break up your calculations

AF Analytics - Preview Results

AF Analytics - Bad and/or Stale Value Filters

AF Analytics - Pass parameters using Attributes

AF Analytics - NoOutput() Function

AF Analytics - Triggers for Event Frames and Notifications

AF Analytics - Units Of Measure (UOMs) in Equations

AF Analytics - Commenting your work

AF Analytics - Filtered calculations

AF Analytics - Advanced write back

AF Analytics - Working with date-time arithmetic

AF Analytics - On Demand calculations

AF Analytics - How to use Attributes from other Elements

 

如何避免个别Analysis在用模板创建的过程中自动启动

在AF 2.8之前,当我们check in对Analysis的修改之后,分析都会自动启动,而有时我们并不想这么做(可能只是想保存所做的修改)。

在AF 2.8或今后版本中,我们加入了一个新的功能,使得这个问题变得简单。您可以选择反选Start analyses when created from a template。这样,Analyses在被手动启动之前不会被启动。

A6.jpg

对于2.8或之前版本有一个tricky的方法,可以参考原文链接Creating Analytics in Element Templates

 

使用分组(Category)功能

将分析分组之后可以更方便的在Analyses plugin中进行管理,而且一个分析可以归属于多个组(category)。比如,下面这个例子中,我们有一个PI AF database,叫做Refinery,有许多Analyses,当我们用模板(Template)来分组时:

B1.jpg

但,如果我们想要backfill所有的Simulation的analyses,靠Analysis Template和Status(运行状态)来筛选就没有办法做到。如果有了合适的分组,就可以通过Category过滤来很方便的找到所有想要启动的分析

B2.jpg

 

让你的计算更有可读性和更容易理解 - 拆分您的计算:

1. 将您的计算分到多个变量中:

下面这个表达式就很长,很不易读

C1.jpg

可以把表达式分拆成多个变量,如下:

C2.jpg

2. 将您的表达式分到多个行中:

C3.jpg

可以通过Shift-Enter来把上面的表达式分行,使之更加可读

C5.jpg

C4.jpg

3. 使用变量来创建您的计算

C5.jpg

上面这个表达式有很多地方使用了重复的表达式(如下面P、T、Dens这三个变量),如果把这些表达式定义为变量,那会使得整个表达式更加可读

C6.jpg

 

预览结果

在启动、回填分析之前,最好都能够预览一下您的结果。只需要选择分析,并右键点击,选择Preview results即可。(注意:分析表达式必须没有syntax error,如果您是用模板创建分析,您需要选择一个样板元素-Example Element)

D1.jpgD2.jpg

在预览中,您将会看到触发的时间戳,所有变量的值(包括所有中间变量,这个例子中,PumpOn、PumpOff、Flowing、Status,而不仅仅是输出最终结果的Staus变量)。

D3.jpg

另外,和常识不同,预览并不需要签入(check in)分析,事实上,在创建分析的过程中频繁签入并不是十分高效的做法,而且每次签入之后,所有在运行的analyses将会重新启动。当看到预览结果之后,您还可以将结果导出到excel中,选择Export Results即可。

D4.jpg

 

坏的以及停滞不变的值

坏的值(Bad Value)和停滞的值可能导致计算失败,或者很多无意义的结果。在写表达式的时候可以通过一些方式来规避他们的影响。

E1.jpg

坏值:通过BadVal来过滤No Data,当BadGasFlow为真的时候,没有结果被写入FixedFlow属性(NoOutput())。

E2.jpg

停滞不变的值:

可以使用HasChanged()函数,使用这个函数的时候需要注意,否则会出现不正确的结果,下面这个例子中,属性Trigger在PI Archive中的值如下图所示,注意到它上一个归档值和前一个值相同,并且已经是在10分钟以前了

E4.jpg

我们现在用HasChanged()来写表达式,并预览,我们可注意到显然最后一个值(在8月3日12点57分53秒的值)显然应该为False

E5.jpg

这是因为这个属性Trigger是被用作触发的,每当这个表达式被出发,有一个新的值会在PI Archive中,所以这个表达式会返回True,尽管实际上这个值并没有改变。

E6.jpg

如果我们修改一下表达式,新加入一个属性,比如Pressure,我们可以用这个属性来触发。这时我们可以得到我们想要的结果。

E7.jpgE8.jpgE9.jpg

什么时候不要使用HasChanged()函数:如果想要用Trigger属性的变化来触发计算,HasChanged()不应该被使用。可以使用prevVal来替代。(尽管不是十分精确)

E11.jpg

 

让您的计算更具适用性,尽量使用属性来传递值,避免写死的数字。

传递时间参数:

F1.jpg

下面两个表达式,分别使用EndTime/OffSet和EndTime/StartTime来计算体积的平均值(您需要使用parseTime()函数,来将string型的原数据转换为时间的格式)

F2.jpg

写死的表达式不需要使用ParseTime()函数,但是灵活性不强。如下:

F3.jpg

传递控制限参数:

子属性最合适用在,关联的PI tag名字、控制限、说明等与父元素相关的地方。这里我们添加了最大值、最小值的子属性,并在计算中引用了这两个属性(Mass|Maximum, Mass|Minimum)。

F4a.jpgF5a.jpg

您也可以在右边Attributes导航栏中找到相应的元素,选择插入相对路径(Relative)

F6.png

 

NoOutput()函数的使用

有的时候NoOutput()可能不会给出期待的结果,参见 KB01127不过这个情况是有意为之的),但大多数时候,使用NoOutput()可以节省资源,如下面这个例子

 

 

G1.jpg

使用NoOutput()函数之后,大大减少了输出的结果,如果这个输出结果要用于新的分析的trigger condition并使用natural trigger,那使用NoOutput()函数之后可以大大减少检查触发条件的次数。

G2.jpg

 

给事件框架和报警创建相同的触发条件

从今年第四季度将会发布的PI AF 2.8.5开始,Notification将会成为Event frame的扩展,采用下面的方法可能就不是必须了。

下面的例子中,我们将在Pump开(On)、关(Off)时触发事件和报警。首先分别创建事件框架和报警的模板

 

H1.jpg

H2.jpg

H3.jpg

 

H4.jpg

创建Pump Status分析来记录Pump的on或者off状态发生切换时的值

H5.jpg

接下来创建事件框架分析来生成相应的事件。

H6.jpg

H7.jpg

 

在计算中指定单位(UOM)以保证获得正确的结果:

比如我们用体积(Vol)和密度(Den)计算质量,现在所有的单位都是匹配的,最终可以得到以磅(lb)为单位的质量

k1.jpg

k2.jpg

但如果我不小心将Mass属性的单位从磅(lb)改为了千克(kg),则我们会得到错误的结果

k3.jpg

但如果我们添加如下新的变量在表达式当中,并将这个新的结果map到Mass属性,则最终得到的结果会是正确的。

k5.jpg

关于UOM的行为, KB01366有更详细的说明

 

给分析添加注释

使用“//”来添加注释

l2.jpg

如果要添加多行注释,可以使用Shift-Enter换行,或者使用“/*”和“*/”

l3.jpg

l4.jpg

余下内容在Part2中继续

接上一个系列:Coresight 系列讨论 - Coresight 安装 ,原文链接为:Coresight Squared – What’s next? Kerberos and more..

 

该系列目前包括三部分:

Server核心版的安装和配置

PI Coresight的安装

Kerberos配置及更多 << 您在这

 

到这里,Coresight已经可以使用了,不过我们这个系列还没有结束,我们将会谈谈大家最喜欢的三头怪(Kerberos是希腊神话中的一个三头狗)Kerberos

声明:

这篇文章中不会涉及Kerberos端口如何工作的技术细节,也不会包含Kerberos的troubleshooting。(可能会在下一次会谈)。与之对应的是我们会将如何利用Kerberos认证和Kerberos委任来加强PI系统的安全性。

 

Coresight - IIS 设置

 

在配置 Kerberos之前,确保 Kerberos 验证在PI Coresight 上是可行的。

 

1. 启用 Windows Authentication(Windows 认证)

选择 Coresight Web Application > Authentication Settings:

2. 启用 Kernel-mode Authentication

右键点击 Windows Authentication > Advanced Settings... > 确保Kernel-mode authentication是被启用的:

 

 

 

 

按照上面的描述,Microsoft 推荐保持启用Kernel-mode 认证。一些关于Kernel-mode 认证对于性能的好处的数据可以在这里看到

 

如果Kernel-mode 认证被:

  • 启用, Kerberos 票据默认用Local System 账户解码.
  • 禁用, Kerberos 票据默认用app pool 账户解码.


这样的话,如果Coresight AppPools以如下用户运行:

  • 虚拟或者机器账户 (比如Network Service) - 不需要更多操作

 

  • 自定义域账户 - 启用 AppPool 账户来解码 Kerberos 票据:
    IIS Manager 中,选择Coresight Web Application > Configuration Editor > 导航到 system.webServer/security/authentication/windowsAuthentication > 设置 useAppPoolCredentialsTrue。这保证了 AppPool 用户是被用户解码传数过来的Kerberos票据的,允许Kerberos认证可以正常工作。

 

3. 使 Kerberos 认证 成为可能

右键点击 Windows Authentication > Providers... > 确保 Negotiate provider是列表中的第一个选项. 比如:

 

Kerberos 简化版: “三个头”

为了尽可能简化,让我们考虑三个主要的关注点:

1. 前端服务器主体名称(SPN,Service Principal Name) - Coresight 终端用户需要能够使用 Kerberos 认证来登录 Coresight (前端服务)

2. 端服务器主体名称(SPN,Service Principal Name)PI 或者 AF Server ( 后端服务) 需要可以接受Kerberos请求 (委任的凭据)

3. Kerberos 委任 - Coresight 服务用户 (前端服务身份) 需要被允许去委任终端用户的凭据给后端服务 (PI or AF Server)

 

第一头: 前端服务器主体名称


一个服务器主体名称是一个服务实例唯一标识符。 服务器主题名称(SPNs)被Kerberos 认证用于将一个服务实例和服务账户联结起来。

服务主体名称能够被分成两部分(这里省略了端口号和服务名,因为它们和我们的内容不大相关)

 

服务类/目标主机, 被安排到一个合适的服务账号.

 

服务类(ServiceClass) – 一个指明服务普通类的字符串。和PI系统有关的常用的服务类包括:

PI Data Archive – PIServer

PI AF Server – AFServer

PI Coresight – HTTP

无论Coresight是不是用了SSL,服务类都是HTTP!

 

目标主机(TargetHost) - 服务运行的机器的主机名和全域名。对Coresight服务器总是既创建两个服务主体名称(SPN)一个给主机名,另一个给全域名!

 

服务账号(ServiceAccount) – 运行服务的用户,如果是:

域账户 - 服务器主体名称(SPN)是被安排给实际存在的运行服务的域用户

虚拟或者机器账号 (Network Service, NT Service\Service 等.) – 服务器主体名称(SPN)被安排给计算机(服务在运行的那台)

 

服务器主体名称(SPNs)可以用 setSPN.exe 工具来创建 (在命令行中可用). 在大多数环境下,创建新的服务器主体名称需要域管理员的权限

 

为了OSI\CoresightSVC 运行的PI Coresight 创建服务器主体名称(SPN), 在 Core01.it.local 机器上运行:

setspn -S HTTP/Core01.it.local OSI\CoresightSVC  
setspn -S HTTP/Core01 OSI\CoresightSVC

为了用Network Service运行的PI Coresight 创建服务器主体名称(SPN),在Core01.it.local 机器上运行:

setspn -S HTTP/Core01.it.local Core01  
setspn -S HTTP/Core01 Core01  

 

用setSPN.exe来验证是否服务器主体名称(SPNs)已经被正确创建和安排。

 

列举安排给某一个域账户所有的服务器主体名称(SPNs):

setspn -L domain\account  
Example: setspn -L OSI\CoresightSVC 

 

检查特定的SPN是否存在

setspn -Q SPN  
Example: setspn -Q HTTP/Core01  

 

第二头: 后端服务器主体名称

 

相同的适用于前端的服务器主体名称的原则也适用于后端。保证合适的 (PIServer/TargetHost and AFServer/TargetHost, 分别) SPNs 被安排给正确的服务用户 (PI Network Manager 服务账号 对应PI Data Archive; PI AF Application Service 的服务账号 对应AF的).

 

第三头: Kerberos委任

 

一旦Kerberos委任被启用,前端服务 (PI Coresight) 可以委任终端用户的凭据给后端服务 (PI Data Archive 或者 PI AF Server) 从而保证终端用户用安全的方法读取后端的数据。

 

在我们的案例中, Coresight 服务用户需要被允许委任终端用户的凭据给 PI Data Archive. 从PI Coresight 2016开始,对AF Server的Keberos 委任对于给Event Frame添加注释是必须的。

 

有三种主要的Kerberos委任的方式:

  • 通常的 (不约束的) 委任
  • 约束委任


  • 按资源的委任

要了解更多信息,请参阅 KB01222 - Types of Kerberos Delegation. 我们将使用最新的方式 – 按照资源的委任。这种方法有多个好处,主要有:

  • 用于委任的权限与后端关联而不是前端
  • 不需要域管理员特权
  • 可以跨域或者跨森林工作

这对于非IT管理员用户是一个非常好的消息,因为它允许后端资源的管理员,如PI Data Archive和PI AF的管理员来控制是否这些资源接受被委任的凭据。 但是要使用基于资源的约束委任也有一些要求。

  • 前端和后端账户域都必须有Server 2012或更高等级的Kerberos分配中心
  • 前端服务器必须安装在Windows Server 2012或更高版本的操作系统上

 

要配置基于资源的约束委任,在Powershell中使用活动目录命令集

下面的例子是给 用域用户 picoresight 运行的PI Coresight和用默认虚拟账户NT Service\AFService 运行的AF Server PIAF01

 

分步指导:

 

1. 获得前端和后端服务标识。如果服务用一个域账户运行,用Get-ADUser。如果用机器账号如 Network Service 或者一个虚拟账号运行, 使用 Get-ADComputer.

$frontendidentity = Get-ADUser -Identity picoresight  
$backendidentity = Get-ADComputer -Identity PIAF01 

2. 安排 front-end identity(前端标识)back-end identity(后端标识)  PrincipalsAllowedToDelegateToAccount 属性

 

Set-ADComputer $backendidentity -PrincipalsAllowedToDelegateToAccount $frontendidentity 

 

3. 观察更新后的后端身份标识的 PrincipalsAllowedToDelegateToAccount 属性,验证它是被合理设置的:

 

Get-ADComputer $backendidentity -Properties PrincipalsAllowedToDelegateToAccount 

 

 

另外:

 

允许多个主体委任给相同的后端,设置 PrincipalsAllowedToDelegateToAccount 给所有需要的标识,以逗号隔开:

 

Set-ADComputer $backendidentity -PrincipalsAllowedToDelegateToAccount $frontendidentity1, $frontendidentity2 

 

 

如果有多个域参与其中,当指定Get-ADUser时指定更多标准,下面这个例子包含了-Server 参数来指定一个标识所在的域控制器:

$frontendidentity = Get-ADUser -Identity picoresight -Server piDC01.pidoge.local 

 

推荐 Kerberos 阅读

OSIsoft: Kerberos Authentication and web browsers

How Windows Server 2012 Eases the Pain of Kerberos Constrained Delegation, Part 1

How Windows Server 2012 Eases the Pain of Kerberos Constrained Delegation, Part 2

If there are multiple domains involved, specify more criteria when executing the Get-ADUser cmdlet. The example below includes the -Server parameter to specify a DC of the domain where the identity resides:

接上一个系列:Coresight 系列讨论 - 在Windows Server核心版安装和配置 ,原文连接为Coresight Squared - Coresight Installation

 

整个系列包括以下3部分:

Coresight 系列讨论 - 在Windows Server核心版安装和配置

PI Coresight安装 << 您现在这里

Kerberos以及更多

 

服务账号

 

在开始安装PI Coresight之前,先准备一个合适的服务账号。为了遵循最小权限的原则,建议准备三个分开的账号:

  1. 运行两个Coresight AppPools的域账号
  2. 运行PI WebAPI Service的域账号
  3. 运行PI Crawler Service的域账号

 

需要的权限:

 

PI Coresight服务账号

- 所有PI Coresight将会需要读取的AF和PI Data Archive内容的读取权限

- 任意PI ProcessBook画面导入文件夹的读取权限

 

PI Crawler 服务账号

- PI Data Archive中PIUSER, PIDBSEC, PIPOINT, PIMAPPINGS 表格的读取权限

- 所有PI Coresight用户将会需要搜索的AF和PI Data Archive内容的读取权限

 

PI Web API 服务账号

- 可以连接任意PI Coresight用到的PI或者AF服务器 (为了 PI Crawler可以成功生成索引)

- AF中事件框架的读取权限(事件框架不被crawler索引, PI Coresight 直接通过PI Web API搜索事件框架)

 

PI Coresight 安装

 

附件中的 setup.ini file (或从pastebin获取)是在Server核心版中安装PI Coresight的钥匙。这个setup 文件已经被修改过了,所以服务器角色和功能的检查已经被跳过了,PI Buffer Subsystem模块也被从安装过程中移除了,因为PI Coresight不需要它。

 

关于 .NET Framework 4.6的重要说明:

考虑到大量关于在安装.Net Framework 4.6过程中报告的缺少前置组件的报告,在安装PI Coresight之前先通过Windows 更新安装,NET Framework 4.6是一个比较好的方案

 

有两种方式完成这一点:

1) 慢:等待Windows更新自动安装

Windows更新是以自动模式运行的(我们在Coresight 系列讨论 - 在Windows Server核心版安装和配置 中已经提到),所以最终所有需要的更新都会被安装。

 

2) 快:在Server核心版上强制执行一个手动更新

要加快这个进程,将Windows Update切换到手动模式,并立刻安装所有可用的更新To speed up the process, switch Windows Update to Manual mode and installed all available Updates immediately:

a) 打开 sconfig > 选择 Option 5 – Windows Update Settings(Windows 升级设置) > 输入 m 切换 Windows Update 到手动模式。

 

b)sconfig 主菜单, 选择 Option 6 – Download and Install Updates(下载和安装更新) > 输入 a 来搜索所有的更新 > 再次输入 a 来安装所有的更新。

有些更新可能需要重启。上述步骤可能需要重复几次直到安装.NET Framework 4.6

 

c) 将Windows 更新切换回到自动模式 (sconfig > 选择 Option 5 – Windows Update Settings (Windows 升级设置)> 输入 a 切换Windows 更新到自动模式)

 

如果您想手动安装,当在Server核心版安装 .NET Framework 4.6 需要使用/q 开关 (静默安装) 。
这样安装将会通过运行 NDP46-KB3045557-x86-x64-AllOS-ENU.exe /q开始。因此,为了以防万一, /q 开关在修改过的 setup.ini 已经包含了

 

按照如下步骤进行操作:

 

1. 替换setup.ini

 

i. 在任意电脑上,运行PI Coresight的安装包,将其解压到一个文件夹(让我们把他叫做installsrc),并且在安装任何组件之前取消安装.

ii. 到 installsrc 路径中用修改过的setup.ini文件替换原有的setup.ini文件。

 

 

2.  将 installsrc 文件夹拷贝到Server核心版的机器上并安装Coresight。

 

使用Robocopy (在命令行中可用) 或者在Windows Explorer当中使用复制粘贴功能。

 

比如,从本地 C:\Temp拷贝Coresight 2016 安装文件夹到一台叫做CORE的远端的机器:

 

robocopy "C:\Temp\Coresight2016" "\\CORES\c$\Coresight2016" /e

 

 

 

使用命令行, 在Server 核心版机器上运行在 installsrc Setup.exe 并按照向导提示安装。PI System Explorer 可以选择不安装,因为它不是必须的,但是他会使得天机PI和AF Server更加容易。不管怎样,PI Coresight到这时已经被安装了。

 

 

3. 用 shutdown /r /t 0 命令重启电脑

 

4. 配置PI Coresight Application Pools让他用一个 service 账户运行

 

如果按照之前的post(Coresight 系列讨论 - 在Windows Server核心版安装和配置远程管理步骤已经完成,配置AppPool会变得容易很多,因为可以通过IIS manager远程操控

 

或者,使用 Powershell. 例如,配置 Coresight AppPools 用 OSI\CoresightSVC:

Import-Module WebAdministration;  
Set-ItemProperty iis:\apppools\coresightserviceapppool -name processModel -value @{userName="OSI\CoresightSVC";password="password";identitytype=3}  
Set-ItemProperty iis:\apppools\coresightadminapppool -name processModel -value @{userName="OSI\CoresightSVC";password="password";identitytype=3} 

 

5. 回收两个Coresight AppPools – 在命令行中运行 iisreset 或者在通过IIS Manager在远端回收AppPool

 

PI Coresight - 安装后工作

 

1. 给PI Coresight 和/或 PI Coresight 管理页面添加权限

 

PI Coresight 应用 - 本地 Windows 用户组 PI Coresight Users 在 PI Coresight 服务器上控制PI Coresight应用的权限。默认情况下经过验证用户组(Authenticated Users group)是其中一个成员。

PI Coresight 管理 - 本地 Windows 用户组 PI Coresight Admins 在 PI Coresight 服务器上控制 PI Coresight 管理的全向。另外一个 PI Coresight 管理员需要是另外一个本地用户组 PI Web API Admins 的成员,以便能允许或者禁用PI Coresight 的PI和AF的来源。默认只用安装Coresight的用户是被加入到这个组当中的.

 

本地用户和组可以从一个工作站上的服务器管理(Server manager)上访问。

 

或者,Poweshell可以被用来添加用户或组到本地用户组当中。比如在CORESIGHTSERVER机器上添加用户 IT.local\AverageJoePI Coresight Admins 本地用户组 ,运行:

$Computer = "CORESIGHTSERVER"  
$Group = "PI Coresight Admins"  
$Domain = "IT.local"  
$UserOrGroup = "AverageJoe"  
([ADSI]"WinNT://$computer/$Group,group").psbase.Invoke("Add",([ADSI]"WinNT://$domain/$userorgroup").path)  

 

Coresight可以配置用任何域或者本地组来运行,具体信息可以参考这篇文档

 

2. 手动创建PI Coresight SQL数据库

 

%pihome64%\Coresight\Admin\SQL 文件夹中所有文件拷贝到一台 SQL Server机器上。

 

使用 robocopy (或者 Windows Explorer复制粘贴功能):

robocopy "C:\Program Files\PIPC\Coresight\Admin\SQL" "\\SQLSERVER\c$\Coresight-SQL" /e  

 

到 SQL Server上,打开一个命令行,导航到含有Coresight SQL文件的文件夹并执行:

GO.BAT DBSERVER DBNAME CoresightAppPoolAccount output.log 

 

比如,在SQL Server MYSQLSERVER上创建一个SQL数据库PICoresight 假设 OSI\CoresightSVC 是运行Coresight AppPools的域账户:

GO.BAT MYSQLSERVER PICoresight OSI\CoresightSVC output.log 

 

对于额外的细节,请参考我们网上的文档

 

最后,连接Coresight应用到Coresight SQL数据库。在网页服务器上,导航到Coresight Admin page > Configuration > PI Coresight Database 并选择合适的SQL Server和SQL数据库

 

注意:因为这个已知的问题, 管理页面会显示Coresight SQL database的连接不健康。要证明这个状况是不是假阳性,到Coresight主页上,创建一个页面并保存,如果这些操作都成功,说明Coresight和SQL Database的连接是没有问题的。

 

3. 在PI Coresight管理页面配置允许的PI和AF数据源

 

额外的PI服务器和AF服务器需要被添加到已知服务器列表(KST)中才能被Coresight使用

 

选项 1) 使用 PI System Explorer (PSE)

 

如果 PSE 在安装AF Client时已经被安装了,可以用它来添加新的PI或者AF服务器。在Server核心版的机器上,使用命令行导航到 %pihome64%\AF (cd /d %pihome64%\AF) 路径下,接着运行AFExplorer.exe.

PSE将会正常打开,在 File > Connections 菜单中可以完成此项工作

 

Option 2) PSE 不可用 - 手动添加PI /AF服务器

 

AF 服务器的信息存储在 AFSDK.config 文件中 (位于 %programdata%\OSIsoft\AF). 在命令行中导航到相应路径,并打开它

cd /d  %programdata%\OSIsoft\AF  
notepad  AFSDK.config  

 

添加一个新的 AF 服务器到 PISystems 集合中:<PISystem name="AFSERVERNAME" host="AFSERVERHOST" protocol="Tcp" port="5457" accountName="" timeout="300" />. Server的唯一 ID 将会在第一次连接时自动生成

 

比如: <PISystem name="CSAFBUILD" host="CSAFBUILD" protocol="Tcp" port="5457" accountName="" timeout="300" /> 将会变成 <PISystem name="CSAFBUILD" id="638f25d4-21ed-44ea-92bb-84305d373578" host="CSAFBUILD" protocol="Tcp" port="5457" accountName="" timeout="300" /> 在第一次连接后

 

 

PI 服务器信息存储在注册表 HKLM\SOFTWARE\PISystem\PI-SDK\1.0\ServerHandles 位置

用Powershell添加新的PI Data Archive - 如果有需要,设置$PI 变量的值。

$PI = "PISERVERNAME"  
$Key = "HKLM:\SOFTWARE\PISystem\PI-SDK\1.0\ServerHandles\" + $PI  
If ( -Not ( Test-Path $Key)){New-Item -Path $Key -ItemType RegistryKey -Force}  
Set-ItemProperty -path $Key -Name "configString" -Type "String" -Value ""  
Set-ItemProperty -path $Key -Name "confirmed" -Type "String" -Value "0"  
Set-ItemProperty -path $Key -Name "defaultServer" -Type "String" -Value "0"  
Set-ItemProperty -path $Key -Name "defaultUser" -Type "String" -Value "pidemo"  
Set-ItemProperty -path $Key -Name "path" -Type "String" -Value $PI  
Set-ItemProperty -path $Key -Name "port" -Type "String" -Value "5450"  
Set-ItemProperty -path $Key -Name "serverID" -Type "String" -Value ""  

 

或者直接在注册表中创建相应的条目

在任何情况下,第一次连接PI Data Archive后ServerID属性会被自动添加。

 

到此为止,PI Coresight + Server 核心版的配置已经全部完成,下一章将介绍 Kerberos配置

stang

AF SDK 开发中级篇

Posted by stang Aug 19, 2016

: 为了更好的利用站内资源营造一个更好的中文开发资源空间,本文为转发修正帖,原作者为OSIsoft技术工程师王曦(Xi Wang),原帖地址:AF SDK 中级篇

 

本帖旨在介绍AF SDK的一些较为复杂函数的用法。

 

以下所有的函数,需要连接好AF服务器,AF数据库,以及一个Element。因此,以下函数将首先完成这个操作:

 

            PISystems AFSDK = new PISystems();                                                                            // 创建连接实例
            PISystem AFServer = AFSDK["XWANG-KQ8HT8KU8"];                                                 // 连接AF服务器
            AFDatabase Connected_Database = AFServer.Databases["training"];                     // 连接AF数据库
            AFElement Connected_Element = Connected_Database.Elements["osisoft"];     // 连接一个Element

 

1. 查询属性的某一时刻值:

 

            AFAttribute myAttribute = Connected_Element.Elements["Beijing"].Elements["test"].Attributes["Attribute2"];  // 指定一个属性
            AFValue Value = myAttribute.GetValue();   

 

2. 查询属性一段时间内的值:

 

            AFTime starttime = new AFTime("*-2h");                                                                                                                         // 定义开始时间
            AFTime endtime = new AFTime("*");                                                                                                                                // 定义结束时间
            AFTimeRange myTimeRange = new AFTimeRange();                                                                                               // 创建时间段实例
            myTimeRange.StartTime = starttime;                                                                                                                             // 确定时间段的开始时间
            myTimeRange.EndTime = endtime;                                                                                                                               // 确定时间段的结束时间

            AFAttribute myAttribute = Connected_Element.Elements["Beijing"].Elements["test"].Attributes["Attribute2"];   // 确定属性名
            AFValues ValueList = myAttribute.GetValues(myTimeRange, 100, null);                                                                 // 将时间段内的值取出

 

3. 按名称查找Element:

 

AFNamedCollectionList<AFElement> myTreeView = AFElement.FindElements(Connected_Database, Connected_Element, "*Beijing*",   AFSearchField.Name, true, AFSortField.Name, AFSortOrder.Ascending, 100);                            // 这个函数比较长,需要的参数比较多对于对应参数的解释请参看AF SDK帮助文件的说明 

 

4. 根据属性查找Element

 

            AFElementTemplate myTemplate = Connected_Database.ElementTemplates["osisoft"];                                                    // 指定需要按属性查找的Element模板
            AFAttributeTemplate myAttribute = myTemplate.AttributeTemplates["speed"];                                                                          // 指定上述模板中的Attribute模板
            AFAttributeValueQuery ValueQuery = new AFAttributeValueQuery(myAttribute, AFSearchOperator.GreaterThan, 0);         // 定义查询的条件
            AFAttributeValueQuery[] ValueArray = {ValueQuery};                                                                                                                        // 将查询条件加入查询条件数组

 

            AFNamedCollectionList<AFElement> myTreeView = AFElement.FindElementsByAttribute(Connected_Element, "*", ValueArray, true, AFSortField.Name, AFSortOrder.Ascending, 100); // 查询element

 

需要注意的是,如果要根据属性来查找Element,这个属性必须基于模板,也就是说,这些找到的Element, 是用模板制作的。

 

下面要讲的部分是AF SDK 2.5新加入的功能。从此版之后,AF SDK将在功能上全面代替PI SDK。在.NET环境中,AF SDK的性能表现也优于PI SDK。

 

1. 连接PI服务器:

 

            PIServers myServers = new PIServers();                                  // 创建实例
            PIServer myServer = myServers["XWANG-KQ8HT8KU8"];    // 连接服务器

 

这个用法与PI SDK极为类似

 

2. 创建点表:

 

             List<PIPoint> myList = new List<PIPoint>();                        // 创建点表实例
            myList.Add(PIPoint.FindPIPoint(myServer, "cdt158"));        // 将需要的点放入点表实例中
            myList.Add(PIPoint.FindPIPoint(myServer, "cdm158"));

 

3. 数据更新:以上两个功能都是为这个功能服务的,这个功能也是AF SDK新增功能中,极为重要的一个,因为它代替了PI SDK中的EVENT PIPE

 

            PIDataPipe myPipe = new PIDataPipe(AFDataPipeType.Snapshot);                                     // 创建一个PIDataPipe实例,用于获取快照值
            myPipe.AddSignups(myList);                                                                                                          // 将点表导入,即获取这些点的快照值
            AFListResults<PIPoint, AFDataPipeEvent> myResults = myPipe.GetUpdateEvents(4);   // 将获取的值导入到 AFListResults类中,用于其他程序调用

 

这里要说明的是:

 

1. myPipe.GetUpdateEvents(4)中的”4“的意思是每个服务器最大的事件采集量,这个DATAPIPE从一个PI服务器中,一次最多获取多少个事件

 

2. 这个功能只能用于从PI服务器中取数,AF属性中连接关系库的数据是不能用这个方法取的

 

3. 用这个功能取数,可以使用时间或事件触发。使用时间触发,推荐使用FOR循环的方式,比较简单;使用事件触发,将会较复杂,需要使用另一个程序进行事件比较,我们会在以后进行更为详细的讨论。

 

***原作到此为止***

 

以下内容是AF SDK 2.6新加入的常用功能简介。

1. AF数据更新

IList<AFAttribute> myList = new IList<AFAttribute>();    // 创建属性表实例
//***IList 中添加属性***

AFDataPipe myPipe = new AFDataPipe()        //创建AFDataPipe实例
myPipe.AddSignups(myList);                         //将属性表导入,即获取该属性的新值
AFListResults<AFAttribute, AFDataPipeEvent> myResults = myPipe.GetUpdateEvents();    //将获取的新值导入到AFListResults类中,用于其他程序调用

 

以下内容是AF SDK 2.7新加入的常用功能简介

1. AF新安全机制

 

2. 未来数据

 

3. 查询式搜索

元素搜索(AFElementSearch)

AFElementSearch eleSearch = new AFElementSearch(AF数据库对象,"对象名称(任选)",“查询字串”);    //创建元素查询式搜索对象
List<AFElement> = eleSearch.FindElements();                                                  //进行搜索,导入元素表实例,用于其他程序调用

Event Frame搜索 (AFEventFrameSearch)

AFEventFrameSearch eleSearch = new AFEventFrameSearch(AF数据库对象,"对象名称(任选)",“查询字串”);    //创建Event Frame查询式搜索对象
List<AFEventFrame> = eleSearch.FindEventFrames();                                                  //进行搜索,导入Event Frame表实例,用于其他程序调用

 

查询字串格式如 Search Query Syntax Overview

 

以下内容是AF SDK 2.8新加入的常用功能简介

1. 数值更改

PI测点数值(PIPoint.ReplaceValues Method )

            AFTime start = new AFTime("-1d");
            AFTime end = new AFTime("*");
            AFTimeRange timeRange = new AFTimeRange(start, end);
            IList<AFValue> myList = new IList<AFValue>();
            //***IList中添加AFValue对象***
            AFErrors<AFValue> afE = new AFErrors<AFValue>();
             afE = myPIPoint.ReplaceValues(timeRange,myList);    //归档里timeRange中的值换成myList中的值

AF属性数值(AFData.ReplaceValues Method )

            AFTime start = new AFTime("-1d");
            AFTime end = new AFTime("*");
            AFTimeRange timeRange = new AFTimeRange(start, end);
            IList<AFValue> myList = new IList<AFValue>();
            //***IList中添加AFValue对象***
            AFErrors<AFValue> afE = new AFErrors<AFValue>();
            afE = myAttribute.Data.ReplaceValues(timeRange,myList);    //属性数据源里timeRange中的值换成myList中的值

 

注:以上方法需要PI Data Archive 2016才能够使用

stang

AF SDK 开发基础篇

Posted by stang Aug 19, 2016

: 为了更好的利用站内资源营造一个更好的中文开发资源空间,本文为转发修正帖,原作者为OSIsoft技术工程师王曦(Xi Wang),原帖地址:AF SDK 基础篇

 

本帖旨在介绍AF SDK的基础功能,包括建立于AF服务器的连接,建立数据库,建立节点,创建节点属性及属性的底层数据连接。本帖所介绍功能不可直接粘贴使用。AF SDK只可使用在.net语言中,因此,以下均为C#程序。

 

连AF服务器

 

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using OSIsoft.AF;
using OSIsoft.AF.Asset;
using OSIsoft.AF.Time;

namespace AF_Element_creation
{
    class Program
    {
        static void Main(string[] args)
        {
            string servername = "XWANG-KQ8HT8KU8";                                                                 // 给一个AF服务器名
            PISystem ConnectedAFServer = AFServerConnect(servername);                                              // 将服务器名作为参数传递给连接函数
            Console.WriteLine("the Connected AF Server is: {0}", ConnectedAFServer.Name.ToString());
            Console.Read();
        }

        static PISystem AFServerConnect(string servername)
        {
            PISystems myAFSDK = new PISystems();                            // 创建一个新的AF SDK实例
            PISystem myAFServer = myAFSDK[servername];                      // 创建AF服务器的链接
            myAFServer.Connect();                                                                    // 连接AF服务器
            return myAFServer;
        }

 

以下的所有函数均需要连接上AF服务器才可使用,因此,连接服务器部分不做重复。

 

1. 创建数据库:

 

static AFDatabase AFDBCreation(string DBName, PISystem ConnectedServer)
        {
            AFDatabase myDB = ConnectedServer.Databases.Add(DBName);                    // 加入一个新的数据库
            return myDB;
        }

 

2. 创建根节点:

 

static AFElement AFRootElementCreation(PISystem ConnectedAFServer)
        {
            AFDatabase ConnectedAFDatabase = ConnectedAFServer.Databases["test"];         // 连接数据库
            AFElement root = ConnectedAFDatabase.Elements.Add("test");                                  // 创建根节点
            root.CheckIn();                                                                                                                          // 执行CheckIn功能
            return root;
        }

 

3. 创建子节点:

 

static AFElement AFElementCreation(PISystem ConnectedAFServer)
        {
            AFDatabase ConnectedAFDatabase = ConnectedAFServer.Databases["test"];                // 连接数据库
            AFElement ConnectedAFElement = ConnectedAFDatabase.Elements["test"];                 // 连接根节点
            lock (ConnectedAFElement)                                                                                                            
            { 
                ConnectedAFElement.CheckOut();                                                                                          // 锁定根节点
            }
            AFElement newElement = ConnectedAFElement.Elements.Add("the first");                      // 创建子节点
            newElement.Description = "this is the description";                                                                // 增加子节点“描述”属性
            ConnectedAFElement.CheckIn();                                                                                                // 解锁子节点
            return newElement;
        }

 

4. 创建属性,及其连接值

 

static AFAttributeCreation(AFElement ConnectedAFElement)
        {
            lock (ConnectedAFElement) {ConnectedAFElement.CheckOut();}                                                                                                                // 锁定子节点
            AFAttribute newAFAttribute_forPIPoint = ConnectedAFElement.Attributes.Add("test for PI Point");                                                          // 创建属性
            newAFAttribute_forPIPoint.Type = typeof(double);                                                                                                                                             // 定义属性数据类型
            AFPlugIn Plugin = ConnectedAFElement.Database.PISystem.DataReferencePlugIns["PI Point"];                                                       // 定义数据连接源为PI数据点
            newAFAttribute_forPIPoint.DataReferencePlugIn = Plugin;                                                                                                                            // 定义属性的数据源连接为PI数据点
            newAFAttribute_forPIPoint.ConfigString = @"\\XWANG-KQ8HT8KU8\cdt158";                                                                                           // 连接PI点的路径

            AFAttribute newAFAttribute_for_RelationalDatabase = ConnectedAFElement.Attributes.Add("test for RelationalDatabase");         // 创建属性
            newAFAttribute_for_RelationalDatabase.Type = typeof(Int32);                                                                                                                      // 定义属性数据类型
            AFPlugIn Plugin2 = ConnectedAFElement.Database.PISystem.DataReferencePlugIns["Table Lookup"];                                          // 定义数据连接源为关系库数据
            newAFAttribute_for_RelationalDatabase.DataReferencePlugIn = Plugin2;                                                                                                // 定义属性的数据源连接为关系库数据
            newAFAttribute_forPIPoint.ConfigString = @"SELECT Number FROM Table1 WHERE people = 'beijing'";                                         // 使用SQL语句连接数据源数据
            ConnectedAFElement.CheckIn();                                                                                                                                                                         // 执行CheckIn功能
        }

 

5. 锁定与释放:

对于AF Element中的各种更改,首先要做的是锁定这个Element为自己操作;当更改完毕后,需要释放这个Element,使之可以被其他人操作。

锁定

private static void LockElement(AFElement e)
        {
            int retries = 0;
            while (retries < 60)
            {
                try
                {
                    lock (e) { e.CheckOut(); }

                    if (e.CheckOutInfo.IsCheckedOutThisThread)
                    {
                        return;
                    }
                }
                catch
                {
                    System.Threading.Thread.Sleep(1000);
                    e.Refresh();
                }

                if (e.CheckOutInfo != null &&
                    e.CheckOutInfo.CheckOutTime.UtcTime < DateTime.UtcNow.AddSeconds(-60))
                    retries++;
            }

            throw new ApplicationException("Cannot obtain lock");
        }:

释放

private static void UnlockElement(AFElement e)
        {
            if (e != null &&
                e.CheckOutInfo != null &&
                e.CheckOutInfo.IsCheckedOutToMe)
            {
                e.CheckIn();
            }
        }:

stang

PI SDK 开发中级篇

Posted by stang Aug 19, 2016

: 为了更好的利用站内资源营造一个更好的中文开发资源空间,本文为转发修正帖,原作者为OSIsoft技术工程师王曦(Xi Wang),原帖地址:PI SDK 中级篇

 

本帖旨在介绍使用PI SDK做一些基本的数据分析,同时,也包括了数据更新的方法,和一些推荐的程序结构。

本帖针对已对PI SDK基础篇比较了解的开发人员。由于OSIsoft在.NET环境下的开发包,已基本由AF SDK取代,因此,本帖只使用C++语言作为PI SDK的开发平台。如果您需要在.NET环境中进行二次开发,请参考AF SDK中级篇。

说明:PI SDK 是过时的技术

 

1. 准备工作

 

在这里的第一段程序,是推荐使用的,进行PI服务器的连接工作,是用子程序的调用方式:

 

static ServerPtr PIServerConnect(_bstr_t servername) 
{
    ::CoInitializeEx(NULL,COINIT_APARTMENTTHREADED);                  // 初始化COM            
    IPISDKPtr spPISDK                                                 // 创建PI SDK连接
    spPISDK.CreateInstance(__uuidof(PISDK));                          // 实例化PI SDK连接
    ServerPtr spServer = spPISDK->GetServers()->GetItem(servername);  // 通过参数,获取连接
    return spServer;                                                  // 返回已连接的服务器指针
}

进行数据的基本分析,需要搜索PI服务器内的点的数据,以下两端子程序,来自PI基础篇的所有点名和搜索点表的子程序:

 

按点名搜索

 

static PIPointPtr GetPIPointsByName(ServerPtr server, _bstr_t tagname)
{
    return server->PIPoints->GetItem(tagname);  // 返回一个PIPoint类型的指针
}

 

按点表搜索:

 

static _PointListPtr SearchPIPoints(ServerPtr server, _bstr_t condition)
{
   return server->GetPoints(condition, NULL); //返回PointList指针类
}

 

2. Variant类型转换: 这是非常重要的部分,后面所有函数的讲解,都要依据此部分的功能

 

PI SDK的大部分函数所需要的参数,都要转换成variant类型,有的传递variant指针,有的传递引用,有的传递二级指针。下面的转换工作将为您展示如何将字符串类型的指针转换成variant类型:

在C++中,字符串指针一般会使用bstr指针类,我们使用这个类型作为例子,进行转换:

 

_bstr_t start = "*-2h";                       // 字符串类起始时间
_variant_t starttime = (_variant_t)start;     // variant类起始时间

 

上面是比较简单的方法,直接做的指针类型强制转换。

下面是做更加通用的方法:

 

_bstr_t start = "*-2h";                                   //起始时间字符串
_PITimeFormatPtr spStart;                                 // 定义PI时间格式指针
spStart.CreateInstance(__uuidof(PITimeFormat));           // 指针实例化
spStart->InputString = start;                             // 指针指向起始时间字符串


VariantInit                                              // 初始化variant指针
V_VT (&starttime) = VT_DISPATCH;                         // 在variant内部类中进行通用指针转换
V_DISPATCH(&starttime) = spStart;                        // 使用dispatch函数,将variant指针指向PI时间格式指针
除了PI的时间,PI的服务器名,PI点名等等,基本都是用这种方法进行格式转换。

 

有了这部分内容后,后面各个函数将省略参数类型转换的功能。

 

功能一:取某一时间段的值(对应PI Datalink中的compressed data功能)

 

static _PIValuesPtr CompressedData (PIPointPtr spPoint, _variant_t starttime, _variant_t endtime)
{    
    return spPoint->Data->RecordedValues(&starttime, &endtime, BoundaryTypeConstants::btAuto, "", FilteredViewConstants::fvRemoveFiltered, NULL);  
}

 

这个函数看似简单,但其中的参数需要说明:

a. 参数starttime和endtime,都是variant &(引用)

b. BoundaryTypeConstants和FilteredViewConstants分别对应的功能就是PI Datalink中的边界类型和标记过滤值的功能

c. 比较不明显的,在参数中,有""参数,它代表的就是PI Datalink中的过滤条件,因为现在为测试,所以过滤条件在这里没有体现

 

功能二:按标准时间间隔显示数据(对应PI Datalink中的采样数据)

 

 

static _PIValuesPtr SampledData (PIPointPtr spPoint, _variant_t starttime, _variant_t endtime)
{    
    return sampled->InterpolatedValues2(&starttime, &endtime, &vtinterval, "", FilteredViewConstants::fvRemoveFiltered, NULL);
}

 

 

此使用的参数与前面一个基本相同,只是多了一个&vtinterval,这个参数同样是variant的引用,意义是采样频率。

 

功能三:数据计算,这部分使用数据在一段时间内,以一个采样频率求和的功能,其他的,类似最大值,最小值等,基本都是使用类似的方法

 

void GetSummariesValues(PIPointPtr spPIPoint, _variant_t vtStart, _variant_t vtEnd, _bstr_t interval)
{
    IPIData2Ptr ipdata2 = (IPIData2Ptr)spPIPoint->Data;                         // 使用PIData2接口类指针
    _NamedValuesPtr summary = ipdata2->Summaries2(vtStart, vtEnd,interval, ArchiveSummariesTypeConstants::asTotal,CalculationBasisConstants::cbEventWeighted,NULL);  // 定义NamedValues指针类
    _variant_t reference = "Total";                                                            
    VARIANT vt_Item = reference;                                                                 // 转换指针为引用
    NamedValuePtr total = summary -> GetItem(&vt_Item);
    spPIValues = (_PIValuesPtr)total->Value;
}

 

这个函数略微有点复杂,原因在于,需要计算的,如和,最大值,最小值,方差等的信息,都存在NamedValues指针类。同时,我们看到了variant指针和variant引用之间的转换方式。

在NamedValue指针类中,使用summary函数,将给定PI点按照时间段和采样频率进行求和。

 

功能四:取值

 

刚才所有的功能,返回的值都是PIValues,也就是类似于一个数组,下面的功能是遍历这个数组中的每一个数:

 

for(long i = 1; i <= spPIValues->Count; i++)
{
    _PIValuePtr spPIValue = spPIValues->GetItem(i);
}

 

这个做法很通俗,就不多讲了

 

功能五:数据更新,在此,默认数据类型是浮点型32位

 

 

HRESULThr = spPIPoint->Data->UpdateValues(spPIValues, DataMergeConstants::dmInsertDuplicates, NULL);

 

数据更新的功能是向PI服务器更新或插入数据,这个函数的使用需要比较小心。

首先,在使用这个函数之前,接口与数据源的数据传递应符合数据源的数据传递协议。当数据到达快照之后,应先使用_PIValuePtr spPIValue = spPIPoint->Data->GetSnapshot()获取点的数据;之后使用

spPIValues->put_ReadOnly(false)将PIValues指针类的写权限打开;然后,spPIValues->Add("*",spPIValue->Value.fltVal + 1,spNVValAttr)将刚才的值写入PIValues指针类;最后,spPIValues->put_ReadOnly(true)将只读打开。

经过上述描述,相信大家已经明白数据更新的过程了。需要说明的是,PIValues指针类可以容纳很多的数据,也就是说,UpdateValues可以支持多点的同时更新。

除了数据的插入,这个函数还可以用作数据替换。您可能已经注意到了dmInsertDuplicates这个参数,同样,如果这个参数被替换成:dmReplaceDuplicates,那么,实现的功能就是替换给定时间的数据。这个时间的设定,就是在spPIValues->Add("*",spPIValue->Value.fltVal + 1,spNVValAttr)中,“*” 表示当前时间,同样,可以使用具体的时间戳进行替换,不过必不可少的就是variant类型的转换。

 

功能六:数据输出更新

 

PI系统的数据传输更新用于向外发送数据,主要使用EVENTPIPE这个工具。如果使用之传输数据,要分两步走

 

1. 创建EVENTPIPE

 

static IEventPipe2Ptr Get_EventPipe (_PointListPtr spPointList)
{
    IEventPipe2Ptr spEventPipe2 = (IEventPipe2Ptr)spPointList->Data->EventPipe;       // 需要使用 IEventPipe2Ptr类型的指针,并且需要已经定义好的点表作为参数,用来明确需要哪些点的数据更新
    spEventPipe2->PollInterval = 2000;                                                // 数据更新频率,单位毫秒
    return spEventPipe2;                                                              // 返回这个指针
}

 

2. 获取数据:

 

void GetValue_EventPipe (EventPipePtr spEventPipe)
{
    while (spEventPipe->Count > 0)
    {
        _PIEventObjectPtr spEventObject = spEventPipe->Take();             // 定义一个PIEventObject类型的指针,获取刚才定义好的EVENTPIPE中的数据
        PointValuesPtr spPointValue = spEventObject->EventData;            // 将这个数据传递给PointValues指针参数
    }
}

 

EVENTPIPE的作用就像一个队列,可以将不同点,不同时间的数据进行存储,当有客户端需要数据时,就把这些数据一次性直接给这个客户端。

 

注释一:PI服务器的值,在C++中的处理

 

PI中存储的值,在C++中是以variant类型存在的,因此,如果需要普通类型的值,可以使用如下的例子,这个例子是OSIsoft德国办公室资深工程师Andreas写的,您可以浏览他的博客,本贴只是加中文注释

 

MyPIValue::MyPIValue (_PIValuePtr pv) {                                                                                            // 将PI的值指针传递进该类,并且对值指针中所包含的内容进行归类分解
       codtTimeStamp = pv->TimeStamp->LocalDate;                                                                               
       bstrTimeStamp = (_bstr_t)codtTimeStamp.Format(_T("%d-%b-%Y %H:%M:%S"));
       DigitalStatePtr tmpDigitalState = NULL;
       IDispatchPtr    tmpDispatch = NULL;
       _PITimePtr      tmpPITime = NULL;
       COleDateTime    tmpTS;
       HRESULT         hr = E_FAIL;

 

       _variant_t vT = pv->Value;                                                                                                              // 过去值指针中的点的数据
       vt = vT.vt;

 

       switch (vT.vt) {
       case VT_I4:                                                                                                                                      // variant VT_I4类存储的是整形32位
              // Int32
              intValue = vT.lVal;
              dblValue = intValue;
              bstrValue = (_bstr_t)intValue;
              break;
       case VT_I2:                                                                                                                                      // variant VT_I2类存储的是整形16位
              // Int16
              intValue = vT.iVal;
              dblValue = intValue;
              bstrValue = (_bstr_t)intValue;
              break;
       case VT_R8:                                                                                                                                    // variant VT_R8类存储的是浮点形64位
              // Float64
              dblValue = vT.dblVal;
              intValue = (int)dblValue;
              bstrValue = (_bstr_t)dblValue;
              break;
       case VT_R4:                                                                                                                                    // variant VT_R4类存储的是浮点形32位
              // Float16/Float32
              dblValue = vT.fltVal;
              intValue = (int)dblValue;
              bstrValue = (_bstr_t)dblValue;
              break;
       case VT_BSTR:                                                                                                                              // variant VT_BSTR类存储的是字符串类
              // String
              bstrValue = vT.bstrVal;
              dblValue = 0;
              intValue = 0;
              break;
       case VT_DISPATCH:                                                                                                                      // variant VT_DISPATCH类存储的是数字类型,这是最复杂的
              // Digital?                                                                                                                                   // 首先需要拿到数字类型表示的内容
              tmpDispatch = vT.pdispVal;
              hr =  tmpDispatch.QueryInterface(__uuidof(DigitalState),&tmpDigitalState);
              if (hr == S_OK) {
                     bstrValue = tmpDigitalState->Name;
                     intValue = tmpDigitalState->Code;
                     dblValue = intValue;
              }
              // Timestamp?                                                                                                                           // 然后然后获取数字类型值的时间戳
              hr =  tmpDispatch.QueryInterface(__uuidof(_PITime),&tmpPITime);
              if (hr == S_OK) {
                           tmpTS = tmpPITime->LocalDate;
                           bstrValue = (_bstr_t)tmpTS.Format(_T("%d %B %Y %H:%M:%S"));
                           intValue = 0;
                           dblValue = 0;
              }
              break;
       default :
              dblValue = 0.0;
              intValue = 0;
              bstrValue = "n/a";
              break;
       }
};

注释二:后续工作---指针清空,关闭COM

 

为了保证没有内存泄露的情况,在程序的最后,需要清空指针,还要进行:

 

::CoUninitialize();

 

用于关闭COM LIBARAY

 

以上是各个功能模块的介绍,下面是一个用PI SDK进行求和工作的完整程序,也是推荐的程序结构方式:

 

#include "stdafx.h"                                    
#include <iostream> 
#include <string>
#include "ATLComTime.h" //for COleDateTime

#import "C:\Program Files\PIPC\PISDK\PISDKCommon.dll" no_namespace
#import "C:\Program Files\PIPC\PISDK\PITimeServer.dll" no_namespace
#import "C:\Program Files\PIPC\PISDK\PISDK.dll" rename("Connected", "PISDKConnected") no_namespace
VOID WINAPI Sleep(_In_ DWORD dwMillisecons);                                                                                                                          // 以上为程序头文件

class MyPIValue                                                                                                                                                                                       // 建立一个PIValue的默认类
{
    _PIValuePtr spPIValue;
public:
    MyPIValue (_PIValuePtr);
    double dblValue;
    int intValue;
    _bstr_t bstrValue;
    _bstr_t bstrTimeStamp;
    COleDateTime codtTimeStamp;
    VARTYPE vt;

};

MyPIValue::MyPIValue (_PIValuePtr pv) {                                                                                                                                               // 建立一个翻译PIValue的类
       codtTimeStamp = pv->TimeStamp->LocalDate;
       bstrTimeStamp = (_bstr_t)codtTimeStamp.Format(_T("%d-%b-%Y %H:%M:%S"));
       DigitalStatePtr tmpDigitalState = NULL;
       IDispatchPtr    tmpDispatch = NULL;
       _PITimePtr      tmpPITime = NULL;
       COleDateTime    tmpTS;
       HRESULT         hr = E_FAIL;

       _variant_t vT = pv->Value;
       vt = vT.vt;

       switch (vT.vt) {
       case VT_I4:
              // Int32
              intValue = vT.lVal;
              dblValue = intValue;
              bstrValue = (_bstr_t)intValue;
              break;
       case VT_I2:
              // Int16
              intValue = vT.iVal;
              dblValue = intValue;
              bstrValue = (_bstr_t)intValue;
              break;
       case VT_R8:
              // Float64
              dblValue = vT.dblVal;
              intValue = (int)dblValue;
              bstrValue = (_bstr_t)dblValue;
              break;
       case VT_R4:
              // Float16/Float32
              dblValue = vT.fltVal;
              intValue = (int)dblValue;
              bstrValue = (_bstr_t)dblValue;
              break;
       case VT_BSTR:
              // String
              bstrValue = vT.bstrVal;
              dblValue = 0;
              intValue = 0;
              break;
       case VT_DISPATCH:
              // Digital?
              tmpDispatch = vT.pdispVal;
              hr =  tmpDispatch.QueryInterface(__uuidof(DigitalState),&tmpDigitalState);
              if (hr == S_OK) {
                     bstrValue = tmpDigitalState->Name;
                     intValue = tmpDigitalState->Code;
                     dblValue = intValue;
              }
              // Timestamp?
              hr =  tmpDispatch.QueryInterface(__uuidof(_PITime),&tmpPITime);
              if (hr == S_OK) {
                           tmpTS = tmpPITime->LocalDate;
                           bstrValue = (_bstr_t)tmpTS.Format(_T("%d %B %Y %H:%M:%S"));
                           intValue = 0;
                           dblValue = 0;
              }
              break;
       default :
              dblValue = 0.0;
              intValue = 0;
              bstrValue = "n/a";
              break;
       }
};



IPISDKPtr       spPISDK = NULL;            /* The PISDK */                                                                                              // 初始化所有需要用的指针
PISDKVersionPtr spSDKVersion = NULL;       /* PI SDK Version */
ServerPtr       spServer = NULL;           /* The Server */
PIPointPtr      spPIPoint = NULL;          /* The PI Point */
_PIValuePtr     spPIValue = NULL; 
_PIValuesPtr     spPIValues = NULL;        /* The PI value */
_PITimeFormatPtr spStartTime = NULL;
_PITimeFormatPtr spEndTime = NULL;

void GetSummariesValues(PIPointPtr spPIPoint, _variant_t vtStart, _variant_t vtEnd, _bstr_t interval)                                // 创建子函数
{
    IPIData2Ptr ipdata2 = (IPIData2Ptr)spPIPoint->Data; 
    _NamedValuesPtr summary = ipdata2->Summaries2(vtStart, vtEnd,interval, ArchiveSummariesTypeConstants::asTotal,CalculationBasisConstants::cbEventWeighted
        ,NULL);
    _variant_t reference = "Total";
    VARIANT vt_Item = reference;
    NamedValuePtr total = summary -> GetItem(&vt_Item);
    spPIValues = (_PIValuesPtr)total->Value;
    for (long i = 1; i <= spPIValues->Count; i++)
    {
        spPIValue = spPIValues->GetItem(i);
        MyPIValue t(spPIValue);
        std::cout << t.bstrTimeStamp << " ";
        std::cout << t.bstrValue << std::endl;
    }
     total.Release();
     summary.Release();
}


int _tmain(int argc, _TCHAR* argv[])
{
       // Initialize COM
       ::CoInitializeEx(NULL,COINIT_APARTMENTTHREADED);
       // Check the command line switches
       if (argc < 6) {
              std::cout << "Command Line:" << std::endl
                        << (_bstr_t)argv[0] << " SERVERNAME TAGNAME starttime endtime interval";
              return (1);
       }
       try                                   
       {
              // Create an instance of the PI SDK                                                                                              // 主函数中连接PI服务器,也可使用子函数调用的方式
              spPISDK.CreateInstance(__uuidof(PISDK));
              // Print out the PI SDK version
              spSDKVersion = spPISDK->PISDKVersion;
              std::cout << std::endl << "PI-SDK Version "
                        << spSDKVersion->Version << " Build "
                        << spSDKVersion->BuildID << std::endl;
              // get the PI Server
              spServer = spPISDK->GetServers()->GetItem((_bstr_t)argv[1]);                                                                    // 从输入参数1中获取PI服务器名
              spPIPoint = spServer->PIPoints->GetItem((_bstr_t)argv[2]);                                                                          // 从输入参数2中获取点名
              spStartTime.CreateInstance (__uuidof(PITimeFormat));
              spEndTime.CreateInstance (__uuidof(PITimeFormat));
              spStartTime->InputString = argv[3];                                                                                                                     // 从输入参数3中获取起始时间
              spEndTime->InputString = argv[4];                                                                                                                      // 从输入参数4中获取截止时间
              _bstr_t interval = argv[5];                                                                                                                                        // 从输入参数5中获取采样频率

              
              _variant_t vtStart;
              VariantInit (&vtStart);
              V_VT (&vtStart) = VT_DISPATCH;
              V_DISPATCH(&vtStart) = spStartTime;

              _variant_t vtEnd;
              VariantInit (&vtEnd);
              V_VT (&vtEnd) = VT_DISPATCH;
              V_DISPATCH(&vtEnd) = spEndTime;

              GetSummariesValues(spPIPoint, &vtStart, &vtEnd, interval);
              // You can use more than just one tagname
              /*for (int ii = 2; ii< argc; ii++) {
                     // Tagname
                     std::cout << (_bstr_t)argv[ii] << std::endl;
                     spPIPoint = spServer->PIPoints->GetItem((_bstr_t)argv[ii]);
                     // Snapshot
                     spPIValue = spPIPoint->Data->Snapshot;
                     MyPIValue mPV(spPIValue);
                     std::cout << mPV.bstrTimeStamp << " ";
                     std::cout << mPV.bstrValue << std::endl;
              }*/
              V_VT (&vtStart) = VT_EMPTY;
              spStartTime.Release();
              V_VT (&vtEnd) = VT_EMPTY;
              spEndTime.Release();                                                                                                                                   // 以下为指针释放,整个程序中最需要注意的部分

             

              spPIValue.Release();
              spPIValues.Release();
              spPIPoint.Release();
              spSDKVersion.Release();
              spPISDK.Release();

       }
       catch( _com_error Err )
       {
              std::cout << "Error: "
                        << Err.Description()
                        << " : "
                        << Err.Error()
                        << std::endl;
              return (1);
       }
       Sleep(5000);
       return 0;
}