1. 程式人生 > >PI Square中文論壇: PI SDK 開發中級篇| PI Square

PI Square中文論壇: PI SDK 開發中級篇| PI Square

注: 為了更好的利用站內資源營造一個更好的中文開發資源空間,本文為轉發修正帖,原作者為OSIsoft技術工程師王曦(Xi Wang),原帖地址:PI SDK 中級篇

 來源:https://d.gg363.site/url?sa=t&rct=j&q=&esrc=s&source=web&cd=4&ved=2ahUKEwjJ3-HLrP3eAhXJULwKHWSqBloQFjADegQIBxAB&url=https%3A%2F%2Fpisquare.osisoft.com%2Fcommunity%2Fall-things-pi%2Fchinese%2Fblog%2F2016%2F08%2F19%2Fpi-sdk-%25E5%25BC%2580%25E5%258F%2591%25E4%25B8%25AD%25E7%25BA%25A7%25E7%25AF%2587&usg=AOvVaw0su5H-KyXXiNMxqaueXYFa

 

本帖旨在介紹使用PI SDK做一些基本的資料分析,同時,也包括了資料更新的方法,和一些推薦的程式結構。

本帖針對已對PI SDK基礎篇比較瞭解的開發人員。由於OSIsoft在.NET環境下的開發包,已基本由AF SDK取代,因此,本帖只使用C++語言作為PI SDK的開發平臺。如果您需要在.NET環境中進行二次開發,請參考AF SDK中級篇。

說明:PI SDK 是過時的技術

 

1. 準備工作

 

在這裡的第一段程式,是推薦使用的,進行PI伺服器的連線工作,是用子程式的呼叫方式:

 

 
  1. static ServerPtr PIServerConnect(_bstr_t servername)   
  2. {  
  3.     ::CoInitializeEx(NULL,COINIT_APARTMENTTHREADED);                  // 初始化COM              
  4.     IPISDKPtr spPISDK                                                 // 建立PI SDK連線  
  5.     spPISDK.CreateInstance(__uuidof(PISDK));                          // 例項化PI SDK連線  
  6.     ServerPtr spServer = spPISDK->GetServers()->GetItem(servername);  // 通過引數,獲取連線  
  7.     return spServer;                                                  // 返回已連線的伺服器指標  
  8. }  

進行資料的基本分析,需要搜尋PI伺服器內的點的資料,以下兩端子程式,來自PI基礎篇的所有點名和搜尋點表的子程式:

 

按點名搜尋:

 

 
  1. static PIPointPtr GetPIPointsByName(ServerPtr server, _bstr_t tagname)  
  2. {  
  3.     return server->PIPoints->GetItem(tagname);  // 返回一個PIPoint型別的指標  
  4. }  

 

按點表搜尋:

 

 
  1. static _PointListPtr SearchPIPoints(ServerPtr server, _bstr_t condition)  
  2. {  
  3.    return server->GetPoints(condition, NULL); //返回PointList指標類  
  4. }  

 

2. Variant型別轉換: 這是非常重要的部分,後面所有函式的講解,都要依據此部分的功能

 

PI SDK的大部分函式所需要的引數,都要轉換成variant型別,有的傳遞variant指標,有的傳遞引用,有的傳遞二級指標。下面的轉換工作將為您展示如何將字串型別的指標轉換成variant型別:

在C++中,字串指標一般會使用bstr指標類,我們使用這個型別作為例子,進行轉換:

 

 
  1. _bstr_t start = "*-2h";                       // 字串類起始時間  
  2. _variant_t starttime = (_variant_t)start;     // variant類起始時間  

 

上面是比較簡單的方法,直接做的指標型別強制轉換。

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

 

 
  1. _bstr_t start = "*-2h";                                   //起始時間字串  
  2. _PITimeFormatPtr spStart;                                 // 定義PI時間格式指標  
  3. spStart.CreateInstance(__uuidof(PITimeFormat));           // 指標例項化  
  4. spStart->InputString = start;                             // 指標指向起始時間字串  
  5.   
  6.   
  7. VariantInit                                              // 初始化variant指標  
  8. V_VT (&starttime) = VT_DISPATCH;                         // 在variant內部類中進行通用指標轉換  
  9. V_DISPATCH(&starttime) = spStart;                        // 使用dispatch函式,將variant指標指向PI時間格式指標  
  10. 除了PI的時間,PI的伺服器名,PI點名等等,基本都是用這種方法進行格式轉換。  

 

有了這部分內容後,後面各個函式將省略引數型別轉換的功能。

 

功能一:取某一時間段的值(對應PI Datalink中的compressed data功能)

 

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

 

這個函式看似簡單,但其中的引數需要說明:

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

b. BoundaryTypeConstants和FilteredViewConstants分別對應的功能就是PI Datalink中的邊界型別和標記過濾值的功能

c. 比較不明顯的,在引數中,有""引數,它代表的就是PI Datalink中的過濾條件,因為現在為測試,所以過濾條件在這裡沒有體現

 

功能二:按標準時間間隔顯示資料(對應PI Datalink中的取樣資料)

 

 

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

 

 

此使用的引數與前面一個基本相同,只是多了一個&vtinterval,這個引數同樣是variant的引用,意義是取樣頻率。

 

功能三:資料計算,這部分使用資料在一段時間內,以一個取樣頻率求和的功能,其他的,類似最大值,最小值等,基本都是使用類似的方法

 

 
  1. void GetSummariesValues(PIPointPtr spPIPoint, _variant_t vtStart, _variant_t vtEnd, _bstr_t interval)  
  2. {  
  3.     IPIData2Ptr ipdata2 = (IPIData2Ptr)spPIPoint->Data;                         // 使用PIData2介面類指標  
  4.     _NamedValuesPtr summary = ipdata2->Summaries2(vtStart, vtEnd,interval, ArchiveSummariesTypeConstants::asTotal,CalculationBasisConstants::cbEventWeighted,NULL);  // 定義NamedValues指標類  
  5.     _variant_t reference = "Total";                                                              
  6.     VARIANT vt_Item = reference;                                                                 // 轉換指標為引用  
  7.     NamedValuePtr total = summary -> GetItem(&vt_Item);  
  8.     spPIValues = (_PIValuesPtr)total->Value;  
  9. }  

 

這個函式略微有點複雜,原因在於,需要計算的,如和,最大值,最小值,方差等的資訊,都存在NamedValues指標類。同時,我們看到了variant指標和variant引用之間的轉換方式。

在NamedValue指標類中,使用summary函式,將給定PI點按照時間段和取樣頻率進行求和。

 

功能四:取值

 

剛才所有的功能,返回的值都是PIValues,也就是類似於一個數組,下面的功能是遍歷這個陣列中的每一個數:

 

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

 

這個做法很通俗,就不多講了

 

功能五:資料更新,在此,預設資料型別是浮點型32位

 

 

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

 

 
  1. static IEventPipe2Ptr Get_EventPipe (_PointListPtr spPointList)  
  2. {  
  3.     IEventPipe2Ptr spEventPipe2 = (IEventPipe2Ptr)spPointList->Data->EventPipe;       // 需要使用 IEventPipe2Ptr型別的指標,並且需要已經定義好的點表作為引數,用來明確需要哪些點的資料更新  
  4.     spEventPipe2->PollInterval = 2000;                                                // 資料更新頻率,單位毫秒  
  5.     return spEventPipe2;                                                              // 返回這個指標  
  6. }  

 

2. 獲取資料:

 

 
  1. void GetValue_EventPipe (EventPipePtr spEventPipe)  
  2. {  
  3.     while (spEventPipe->Count > 0)  
  4.     {  
  5.         _PIEventObjectPtr spEventObject = spEventPipe->Take();             // 定義一個PIEventObject型別的指標,獲取剛才定義好的EVENTPIPE中的資料  
  6.         PointValuesPtr spPointValue = spEventObject->EventData;            // 將這個資料傳遞給PointValues指標引數  
  7.     }  
  8. }  

 

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

 

為了保證沒有記憶體洩露的情況,在程式的最後,需要清空指標,還要進行:

 

 
  1. ::CoUninitialize();  

 

用於關閉COM LIBARAY

 

以上是各個功能模組的介紹,下面是一個用PI SDK進行求和工作的完整程式,也是推薦的程式結構方式:

 

 
  1. #include "stdafx.h"                                      
  2. #include <iostream>   
  3. #include <string>  
  4. #include "ATLComTime.h" //for COleDateTime  
  5.   
  6. #import "C:\Program Files\PIPC\PISDK\PISDKCommon.dll" no_namespace  
  7. #import "C:\Program Files\PIPC\PISDK\PITimeServer.dll" no_namespace  
  8. #import "C:\Program Files\PIPC\PISDK\PISDK.dll" rename("Connected", "PISDKConnected") no_namespace  
  9. VOID WINAPI Sleep(_In_ DWORD dwMillisecons);                                                                                                                          // 以上為程式標頭檔案  
  10.   
  11. class MyPIValue                                                                                                                                                                                       // 建立一個PIValue的預設類  
  12. {  
  13.     _PIValuePtr spPIValue;  
  14. public:  
  15.     MyPIValue (_PIValuePtr);  
  16.     double dblValue;  
  17.     int intValue;  
  18.     _bstr_t bstrValue;  
  19.     _bstr_t bstrTimeStamp;  
  20.     COleDateTime codtTimeStamp;  
  21.     VARTYPE vt;  
  22.   
  23. };  
  24.   
  25. MyPIValue::MyPIValue (_PIValuePtr pv) {                                                                                                                                               // 建立一個翻譯PIValue的類  
  26.        codtTimeStamp = pv->TimeStamp->LocalDate;  
  27.        bstrTimeStamp = (_bstr_t)codtTimeStamp.Format(_T("%d-%b-%Y %H:%M:%S"));  
  28.        DigitalStatePtr tmpDigitalState = NULL;  
  29.        IDispatchPtr    tmpDispatch = NULL;  
  30.        _PITimePtr      tmpPITime = NULL;  
  31.        COleDateTime    tmpTS;  
  32.        HRESULT         hr = E_FAIL;  
  33.   
  34.        _variant_t vT = pv->Value;  
  35.        vt = vT.vt;  
  36.   
  37.        switch (vT.vt) {  
  38.        case VT_I4:  
  39.               // Int32  
  40.               intValue = vT.lVal;  
  41.               dblValue = intValue;  
  42.               bstrValue = (_bstr_t)intValue;  
  43.               break;  
  44.        case VT_I2:  
  45.               // Int16  
  46.               intValue = vT.iVal;  
  47.               dblValue = intValue;  
  48.               bstrValue = (_bstr_t)intValue;  
  49.               break;  
  50.        case VT_R8:  
  51.               // Float64  
  52.               dblValue = vT.dblVal;  
  53.               intValue = (int)dblValue;  
  54.               bstrValue = (_bstr_t)dblValue;  
  55.               break;  
  56.        case VT_R4:  
  57.               // Float16/Float32  
  58.               dblValue = vT.fltVal;  
  59.               intValue = (int)dblValue;  
  60.               bstrValue = (_bstr_t)dblValue;  
  61.               break;  
  62.        case VT_BSTR:  
  63.               // String  
  64.               bstrValue = vT.bstrVal;  
  65.               dblValue = 0;  
  66.               intValue = 0;  
  67.               break;  
  68.        case VT_DISPATCH:  
  69.               // Digital?  
  70.               tmpDispatch = vT.pdispVal;  
  71.               hr =  tmpDispatch.QueryInterface(__uuidof(DigitalState),&tmpDigitalState);  
  72.               if (hr == S_OK) {  
  73.                      bstrValue = tmpDigitalState->Name;  
  74.                      intValue = tmpDigitalState->Code;  
  75.                      dblValue = intValue;  
  76.               }  
  77.               // Timestamp?  
  78.               hr =  tmpDispatch.QueryInterface(__uuidof(_PITime),&tmpPITime);  
  79.               if (hr == S_OK) {  
  80.                            tmpTS = tmpPITime->LocalDate;  
  81.                            bstrValue = (_bstr_t)tmpTS.Format(_T("%d %B %Y %H:%M:%S"));  
  82.                            intValue = 0;  
  83.                            dblValue = 0;  
  84.               }  
  85.               break;  
  86.        default :  
  87.               dblValue = 0.0;  
  88.               intValue = 0;  
  89.               bstrValue = "n/a";  
  90.               break;  
  91.        }  
  92. };  
  93.   
  94.   
  95.   
  96. IPISDKPtr       spPISDK = NULL;            /* The PISDK */                                                                                              // 初始化所有需要用的指標  
  97. PISDKVersionPtr spSDKVersion = NULL;       /* PI SDK Version */  
  98. ServerPtr       spServer = NULL;           /* The Server */  
  99. PIPointPtr      spPIPoint = NULL;          /* The PI Point */  
  100. _PIValuePtr     spPIValue = NULL;   
  101. _PIValuesPtr     spPIValues = NULL;        /* The PI value */  
  102. _PITimeFormatPtr spStartTime = NULL;  
  103. _PITimeFormatPtr spEndTime = NULL;  
  104.   
  105. void GetSummariesValues(PIPointPtr spPIPoint, _variant_t vtStart, _variant_t vtEnd, _bstr_t interval)                                // 建立子函式  
  106. {  
  107.     IPIData2Ptr ipdata2 = (IPIData2Ptr)spPIPoint->Data;   
  108.     _NamedValuesPtr summary = ipdata2->Summaries2(vtStart, vtEnd,interval, ArchiveSummariesTypeConstants::asTotal,CalculationBasisConstants::cbEventWeighted  
  109.         ,NULL);  
  110.     _variant_t reference = "Total";  
  111.     VARIANT vt_Item = reference;  
  112.     NamedValuePtr total = summary -> GetItem(&vt_Item);  
  113.     spPIValues = (_PIValuesPtr)total->Value;  
  114.     for (long i = 1; i <= spPIValues->Count; i++)  
  115.     {  
  116.         spPIValue = spPIValues->GetItem(i);  
  117.         MyPIValue t(spPIValue);  
  118.         std::cout << t.bstrTimeStamp << " ";  
  119.         std::cout << t.bstrValue << std::endl;  
  120.     }  
  121.      total.Release();  
  122.      summary.Release();  
  123. }  
  124.   
  125.   
  126. int _tmain(int argc, _TCHAR* argv[])  
  127. {  
  128.        // Initialize COM  
  129.        ::CoInitializeEx(NULL,COINIT_APARTMENTTHREADED);  
  130.        // Check the command line switches  
  131.        if (argc < 6) {  
  132.               std::cout << "Command Line:" << std::endl  
  133.                         << (_bstr_t)argv[0] << " SERVERNAME TAGNAME starttime endtime interval";  
  134.               return (1);  
  135.        }  
  136.        try                                     
  137.        {  
  138.               // Create an instance of the PI SDK                                                                                              // 主函式中連線PI伺服器,也可使用子函式呼叫的方式  
  139.               spPISDK.CreateInstance(__uuidof(PISDK));  
  140.               // Print out the PI SDK version  
  141.               spSDKVersion = spPISDK->PISDKVersion;  
  142.               std::cout << std::endl << "PI-SDK Version "  
  143.                         << spSDKVersion->Version << " Build "  
  144.                         << spSDKVersion->BuildID << std::endl;  
  145.               // get the PI Server  
  146.               spServer = spPISDK->GetServers()->GetItem((_bstr_t)argv[1]);                                                                    // 從輸入引數1中獲取PI伺服器名  
  147.               spPIPoint = spServer->PIPoints->GetItem((_bstr_t)argv[2]);                                                                          // 從輸入引數2中獲取點名  
  148.               spStartTime.CreateInstance (__uuidof(PITimeFormat));  
  149.               spEndTime.CreateInstance (__uuidof(PITimeFormat));  
  150.               spStartTime->InputString = argv[3];