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伺服器的連線工作,是用子程式的呼叫方式:
- 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(
"*"
,
中,“*” 表示當前時間,同樣,可以使用具體的時間戳進行替換,不過必不可少的就是variant型別的轉換。spPIValue
->Value.fltVal + 1,spNVValAttr)
功能六:資料輸出更新
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];