Skip navigation
All Places > All Things PI - Ask, Discuss, Connect > PI Square中文论坛 > Blog > Author: stang

PI Square中文论坛

4 Posts authored by: stang
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;
}
stang

PI SDK 开发基础篇

Posted by stang Aug 19, 2016

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

 

本帖旨在介绍使用PI SDK进行开发之初的基本功能。如服务器连接,搜索PI点,读取快照值,读取归档值等。基本程序提供C++C#两种语言的编写方法。这两种语言都需调用PI SDK函数包中一些基本函数库:PISDKPISDKCommonPITimeServer。

说明:本帖中的程序段仅适用于功能性的介绍,不宜直接复制使用,因为程序段之中没有加入任何错误警告功能或抛出例外

说明:PI SDK 是过时的技术

 

 

1. 连PI接服务器

 

C++ 程序

 

#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

IPISDKPtr       spPISDK = NULL; //定义新的PISDK指针类
ServerPtr       spServer = NULL; //定义新的PI服务器指针类

// 定义PI服务器连接函数 --- PIServerConnet()

static ServerPtr PIServerConnect(_bstr_t servername)
{
    ::CoInitializeEx(NULL,COINIT_APARTMENTTHREADED); //初始化COM
    spPISDK.CreateInstance(__uuidof(PISDK)); //在COM中初始化新的PISDK指针类
    spServer = spPISDK->GetServers()->GetItem(servername);  //通过传递进来的服务器名来连接该服务器。   注:需要使用_bstr_t字符串类型参数
    return spServer; //返回PI服务器指针
}

int _tmain(int argc, _TCHAR* argv[])
{
    _bstr_t servername = "XWANG-KQ8HT8KU8"; //定义服务器名。
    spServer = PIServerConnect(servername); //接收返回的的PI服务器指针类型
    return 0;
}

 

C#程序

 

using System;
 using System.Collections.Generic;
 using System.Linq;
 using System.Text;
 using PISDK;
 using PISDKCommon;
 using PITimeServer;

namespace DemPISDK
 {
    class Program
    {
        static void Main(string[] args)
        {
            Server server = ConnectToServer("servername");  //获取返回的PI服务器类型
        }

         private static Server ConnectToServer(string servername)
         {
             PISDK.PISDK pisdk = new PISDK.PISDK();            //定义新的PISDK类
             Server server = null; //定义新的PI服务器类
             server = pisdk.Servers[servername]; //获取服务器名
            server.Open(); //连接PI数据库
             return server;      
         }
    }
}

 

以下所有的功能都将在连接上PI 服务器之后才能实现,因此,将省略PI服务器连接这一部分

 

2. 创建PI点

 

创建单点

 

C++ 程序

 

static PIPointPtr CreatePIPoint (ServerPtr spServer)
 {
    _NamedValuesPtr nvAttribues = NULL; // 定义PI点的属性
    return spServer->PIPoints->Add("test1","classic",PointTypeConstants::pttypFloat32,nvAttribues); // 创建PI点
 }

 

C# 程序

 

private static PIPoint CreatePIPoint ( Server server)
{
    NamedValues nvAttributes nvAttributes = new NamedValues(); // 定义PI点的属性
    return server.PIPoints.Add("test1", "classic", PointTypeConstants.pttypeFloat32, nvAttributes); // 创建PI点
}

创建多点

 

C++程序

 

static void PointsCreation (ServerPtr spServer)
{
    IPIPoints2Ptr spipipoints2 = (IPIPoints2Ptr)spServer->PIPoints; // 创建 IPIPoints2指针,并让其指向传入ServerPtr指针的那片区域

     _variant_t description = "test"; // 创建“描述”属性的variant类型
     _variant_t engunits = "m"; // 创建“工程单位”属性的variant类型
     _variant_t classtype = "classic"; // 创建“点的种类”属性的variant类型
     _variant_t pt = PointTypeConstants::pttypFloat32; // 创建“点的数据类型”属性的variant类型
 
     _NamedValuesPtr nvAttributes; // 创建一个指向点属性的指针
    nvAttributes.CreateInstance(__uuidof(NamedValues)); // 为这个指针创建一片空间,用于存储点的属性
    nvAttributes->Add("descriptor", &description); // 增加“描述”属性
    nvAttributes->Add("engunits", &engunits);
    nvAttributes->Add("ptclassName", &classtype);
    nvAttributes->Add("pointType", &pt);
 
     _variant_t var_nvAttributes; // 创建一个variant类型的指针
    VariantInit(&var_nvAttributes); // 初始化对于上面指针的二级指针为VT_EMPTY
     V_VT (&var_nvAttributes) = VT_DISPATCH; // 将这个二级指针转变为VT_DISPATCH类型
     V_DISPATCH(&var_nvAttributes) = nvAttributes; // 将这个二级指针指向点属相的指针
 
     _NamedValuesPtr nvPoints; // 创建一个定义PI点表的指针
    nvPoints.CreateInstance(__uuidof(NamedValues)); // 为该指针定义一片空间
     nvPoints->Add("test2", &var_nvAttributes); // 加入点名,及其属性
     nvPoints->Add("test3", &var_nvAttributes);
 
     _PIErrorsPtr Errors; // 创建一个PIError的指针
     Errors.CreateInstance(__uuidof(PIErrors)); // 为该指针定义一片空间
     _PIErrors **ErrorsPtr = &Errors; // 为该指针定义一个二级指针
 
     _PointListPtr PointList; // 创建一个PointList的指针
    PointList.CreateInstance(__uuidof(PointList)); // 为该指针定义一片空间
     _PointList **PointListPtr = &PointList; // 为该指针定义一个二级指针 
    spipipoints2->AddTags(nvPoints,ErrorsPtr,PointListPtr,NULL); // 使用之前定义的IPIPoints2类型的指针加入点表
}

这个程序相对较复杂,主要使用了C++中的标准DISPATCH接口类型

 

C#程序

 

private static PointList CreatePIPoints(Server server)
 {
    NamedValues nvTags = new NamedValues();
 
     NamedValues nvAttributes = new NamedValues();
 
    nvAttributes.Add("descriptor", "Test Description");
    nvAttributes.Add("ptclassname", "classic");
    nvAttributes.Add("pointsource", "L");
    nvAttributes.Add("pointtype", PointTypeConstants.pttypFloat32);
 
    nvTags.Add("testAddTag", nvAttributes);
    nvTags.Add("testAddTags001", nvAttributes);
    nvTags.Add("testAddTags002", nvAttributes);
    nvTags.Add("testAddTags003", nvAttributes);
 
     PIErrors errs = new PIErrors();
     PointList ptlist = new PointList();
     IPIPoints2 ipipoints2 = (IPIPoints2)server.PIPoints;
    ipipoints2.AddTags(nvTags, out errs, out ptlist);
 
     return ptlist;
 }

相比起C++程序,C#程序更为通俗

 

这两段程序值得注意的是,在点的创建过程中,点属性中的pointclass和pointtype是必须创建并设定的,如果没有,所有的点是不能被创建的

 

3. 搜点

 

C++程序

 

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

 

C#程序:

 

private static PIPoint GetPIPointByName(Server server, String tagname)
 {
    return server.PIPoints[tagname]; // 返回一个PIPoint类型的指针
 }

 

按条件搜点表


C++程序

 

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

 

C#程序

 

private static PointList SearchPIPoints(Server server, String condition)
 {
    return server.GetPoints(condition);                   
 }

4. 编辑点

 

按编辑单点的特定属性

 

C++程序

 

static void TagEdit (ServerPtr spServer)
{
    _bstr_t tagname = "test2"; // 给出点名
     PIPointPtr spPoint; // 创建PIPointPtr类指针接收点的一切信息
     spPoint = spServer->PIPoints->GetItem(tagname); // 将点名传给spPoint指针
 
     _bstr_t tag_description = "descriptor"; // 定义需要调整的属性
     PointAttributePtr pa_attribute = spPoint->PointAttributes->GetItem(tag_description);   // 将属性指针指向之前定义的需要调整的属性
     spPoint->PointAttributes->ReadOnly = VARIANT_FALSE; // 将只读属性关闭
     pa_attribute->Value = "test for tag edit"; // 更改需要调整属性的值
     spPoint->PointAttributes->ReadOnly = VARIANT_TRUE; // 将只读属性打开
}

C# 程序

 

private static void EditPIPoint(PIPoint pt)
 {
     Server server = new Server;

     server.PIPoints[test2];

     PointAttribute attr = pt.PointAttributes["descriptor"];
    pt.PointAttributes.ReadOnly = false;
     attr.Value = "new description";
    pt.PointAttributes.ReadOnly = true;
 }

编辑一组点

 

C++程序

 

static void EditPoints (ServerPtr, spServer)
{
    _bstr_t condition = "tag='test*'"; // 给定编辑点的搜索条件
     _PointListPtr ptlist; // 创建一个点表指针
     ptlist = spServer->GetPoints(condition,NULL); // 将按条件搜索出的点存储到点表里
     _NamedValuesPtr nvTags; // 创建一个接收点表信息的指针
    nvTags.CreateInstance(__uuidof(NamedValues)); // 分配内存区域
     _NamedValuesPtr nvAttributes; // 创建一个接收点属性的指针
     nvAttributes.CreateInstance(__uuidof(NamedValues)); // 分配内存区域
     _variant_t descriptions = "test for points edit"; // 将需要更改的信息创建成 _variant_t类型
 
     nvAttributes->Add("descriptor", &descriptions); // 将修改的信息存储至之前创建的接收点属性的指针指向的区域
     _variant_t var_attributes; // 创建一个新的_variant_t类型参数
    VariantInit(&var_attributes); // 初始化_variant_t类型参数 
     V_VT(&var_attributes) = VT_DISPATCH; // 将_variant_t类型参数转变成VT_DISPATCH接口类型参数
     V_DISPATCH(&var_attributes) = nvAttributes; // 将VT_DISPATCH接口类型参数等同于之前存储属性的那片区域
 
     for (int i = 1; i <= ptlist->Count; i++) // 这个FOR循环是为将符合条件的所有的点的点名和属性加入到指向点表信息指针指向的内存区域
     {
         _variant_t vi = i;
         PIPointPtr spTag = ptlist->Item[&vi];
         nvTags->Add(spTag->Name, &var_attributes);
     }
 
     _PIErrorsPtr Errors; // 需要实例化一个PIErrors
     Errors.CreateInstance(__uuidof(PIErrors));
     _PIErrors **ErrorsPtr = &Errors; // 用一个二级指针指向这个实例
 
     IPIPoints2Ptr ipi_points2 = (IPIPoints2Ptr)spServer->PIPoints;  // 使用 IPIPoints2Ptr类型指针指向点表
     ipi_points2->EditTags(nvTags, &Errors, NULL); // 使用上述指针类里的编辑点的函数,将点表中所有的点的信息做更改
}

 

C#程序

 

private static void EditPIPoints(Server server, PointList ptlist)
 {
     NamedValues nvTags = new NamedValues();
     NamedValues nvAttributes = new NamedValues();
 
    nvAttributes.Add("descriptor", "edited description");

    foreach(PIPoint pt in ptlist)
     {
        nvTags.Add(pt.Name, nvAttributes);
     }
 
     PIErrors errs = new PIErrors();
 
     IPIPoints2 ipipoints2 = (IPIPoints2)server.PIPoints;
     ipipoints2.EditTags(nvTags, out errs);
 
     foreach (PIError err in errs)
     {
        Console.WriteLine(err.Description);
     }
 }

在C#程序中,没有写点表如何获得的,只是直接调用之前按条件搜索点表的程序,原理是一样的

 

以下内容为如何从PI服务器中取出点/点表的一些值。这些程序是基于点的操作,因此,将不再重复如何搜索点/点表。如果使用下述函数,请首先参看本帖第三部分---搜索点/点表

 

5. 取快照值

 

取单快照值

 

C++程序

 

static _PIValuePtr GetSnapshot(PIPointPtr pt)         // 传入点信息
 {
    return (_PIValuePtr)pt->Data->GetSnapshot();
 }

 

C#程序

 

private static PIValue GetSnapshot(PIPoint pt)
 {
    return pt.Data.Snapshot;
 }

取点表快照

 

C++程序

 

static PointValuesPtr GetSnapshot(_PointListPtr sppointlist) // 传入点表信息
 {
    PointValuesPtr ptValues = sppointlist->Data->GetSnapshot(NULL);
    return ptValues;
 }

 

C#程序

 

 

private static PointValues GetSnapshot(PointList ptlist)
 {
    NamedValues nvErrs = new NamedValues();
    PointValues ptvalues = ptlist.Data.get_Snapshot(out nvErrs);
 }

 

6. 取某时刻历史数据

单点某时数据

 

C++程序

 

static _PIValuePtr GetArchive(PIPointPtr pt)
 {
    _bstr_t time = "*-2h"; // 以下内容为将字符串时间转化为_variant_t 类型的时间
    _PITimeFormatPtr sptime;
    sptime.CreateInstance (__uuidof(PITimeFormat));
    sptime->InputString = time;
    _variant_t vtStart;
    VariantInit (&vtStart);
    V_VT (&vtStart) = VT_DISPATCH;
    V_DISPATCH(&vtStart) = sptime; 
    return (_PIValuePtr)pt->Data->ArcValue(vtStart, RetrievalTypeConstants::rtAuto, NULL);      // 将PI点指针类型的指针转换成PI Value类型的指针,并执行函数。
 }

C#程序

 

private static PIValue GetArchive(PIPoint pt)
 { 
    PITimeFormat time = new PITimeFormat();
    time.InputString = "*-2h";
    return pt.Data.ArcValue(time, RetrievalTypeConstants.rtAuto);
 }