みんなさん最近元気ですか?もうBeckhoff TF6100 第5話になりましたね。前回はPLC OPENライブラリ経由でOPC ServerのNodeを読み書きしました。次はMethod Callになりますね。ちなみにこちらは第4話です。
Method Serviceとは?
OPC UAのMethod Callに関してはこのLINKして詳しい説明があります。
https://reference.opcfoundation.org/v104/Core/docs/Part4/5.11.2/
まず簡単に言いますと、
This Service is used to call (invoke) a list of Methods.
This Service provides for passing input and output arguments to/from a Method. These arguments are defined by Properties of the Method.
つまるこのServiceはMethodを呼び出し、InputとOutput Argumentsが提供されます。それらのパラメタはMethodのプロパティ(Properties)から決めます。
そしてMethodは関数だと思ってください。
パラメタ
Methodを呼び出すためにはいくつのパラメタが必要となり、Methodの戻り値にもいくつがあります。
https://reference.opcfoundation.org/v104/Core/docs/Part4/5.11.2/#Table65
Request
こちらはRequestつまりInputです。右側にオレンジについてるパラメタではBeckhoffのFunction Blockで自分で構成する必要があります。(変数名は100%一致しないですが)
requestHeader | RequestHeader | |
methodsToCall [] | CallMethodRequest | O |
objectId | NodeId | O |
methodId | NodeId | O |
inputArguments [] | BaseDataType | O |
Response
こちらはResponseつまりOutputになります。右側にオレンジについてるパラメタではBeckhoffのFunction BlockのOutputになります。(変数名は100%一致しないですが)
responseHeader | ResponseHeader | |
results [] | CallMethodResult | O |
statusCode | StatusCode | O |
inputArgumentResults [] | StatusCode | O |
inputArgumentDiagnosticInfos [] | DiagnosticInfo | O |
outputArguments [] | BaseDataType | O |
diagnosticInfos [] | DiagnosticInfo | O |
Support DataType
Method Call
Beckhoff TF6100でMethod Callを実装するにはもちろんOPCUA Serverと接続のUA_Connect・UA_Disconnectにも必要ですが、ほかにもUA_MethodGetHandle・UA_MethodReleaseHandle・UA_MethodCallにも使われています。
ですが、前のPOST見た方にはNodeの読み書きの流れとはあまり変わりません。
流れ
OPCUA Serverと接続するまでいつも通りです。そしてMethod CallするにはUA_MethodGetHandleからMethodの情報・Objectの情報などを構成します。
UA_MethodCallをInput Arguments(pInputArgInfo,pInputArgData),Output Arguments(pOutputArgInfo,pOutputArgData)を構成しMethodを呼び出します。
Methodからの戻り値がpOutputArgInfoAndDataに入ります。pOutputArgInfoAndData
はPointerでMethodの戻り値の構造体が入っています。
最後にUA_MethodReleaseHandleを呼び出し、MethodのConnectionを解放します。
Method Call テスト
UaExpertからCallしたいMethodを右クリックし、Call..をクリックします。
値など入れCallボタン押せばOKです。
UA_MethodGetHandle
このFunctionBlockはUA_MethodCallを使用するためのMethodHdIを作成します。
VAR_INPUT | ||
Execute | BOOL | 立ち上げてコマンド実行 |
ConnectionHdl | DWORD | OPCUA Method Handle |
ObjectID | ST_UANodeID | Object Node ID |
MethodNodeID | ST_UANodeID | Method NodeID |
Timeout | TIME | Timeout設定 |
VAR_OUTPUT | ||
MethodHdl | DWORD | OPC UA Method Handle、Method Callなどで使う変数です。 |
Done | BOOL | エラーなしで実行OK |
Busy | BOOL | 実行中 |
Error | BOOL | エラーあり |
ErrorID | DWORD | エラーID |
ST_UANodeID
nNamespaceIndex | UINT | UA_GetNamespaceIndexからのNamespaceIndex |
nReserved | ARRAY [1..2] OF BYTE | |
sIdentifier | STRING(MAX_STRING_LENGTH) | NodeIDのIdentifierです。今回はDemo.Methodになります。 |
eIdentifierType | E_UAIdentifierType | NodeIdのIdentifier種類です。文字列か、数字か。今回は文字列です。 |
UA_MethodCall
かなり長いFunction Blockですが、これはUA_MethodCallになります。このFunction Blockを使ってMethodを呼び出します。
VAR_INPUT | ||
Execute | BOOL | 立ち上げてコマンド実行 |
ConnectionHdl | DWORD | OPCUA Connection Handle |
MethodHdl | DWORD | UA_MethodGetHandleから |
nNumberOfInputArguments | UDINT | InputArgumentsの数 |
pInputArgInfo | POINTER TO ST_UAMethodArgInfo | InputArgumentsの情報を保存する変数のMemoryアドレス |
cbInputArgInfo | UDINT | InputArgumentsの情報を保存する変数のMemory Size |
pInputArgData | PVOID | InputArgumentsの値を保存する変数のMemoryアドレス(固定長さ) |
cbInputArgData | UDINT | InputArgumentsの値を保存する変数のMemory Size(固定長さ) |
pInputWriteData | PVOID | InputArgumentsの値を保存する変数のMemoryアドレス(可変長さ) |
cbInputWriteData | UDINT | InputArgumentsの値を保存する変数のMemory Size(可変長さ) |
nNumberOfOutputArguments | UDINT | OutputArgumentsの数 |
pOutputArgInfo | POINTER TO ST_UAMethodArgInfo | OutputArgumentsの情報を保存する変数のMemoryアドレス |
cbOutputArgInfo | UDINT | OutputArgumentsの値を保存する変数のMemory Size |
pOutputArgInfoAndData | PVOID | OutputArgumentsの値(バイド配列)のMemoryアドレス |
cbOutputArgInfoAndData | UDINT | OutputArgumentsの値(バイド配列)のMemory Size |
Timeout | TIME | |
VAR_OUTPUT | ||
cbRead_R | UDINT | Total SeverからのデータByte数 |
Done | BOOL | エラーなしで実行OK |
Busy | BOOL | 実行中 |
Error | BOOL | エラーあり |
ErrorID | DWORD | エラーID |
ST_UAMethodArgInfo
DataType | E_UADataType | MethodのUA Type |
ValueRank | DINT | パラメタががScalarか配列か |
ArrayDimensions | ARRAY[1..3] OF UDINT | 配列ならその構造 |
nLenData | DINT | Arguments変数の長さ(構造体だけは必要になります。) |
UA_MethodReleaseHandle
VAR_INPUT | ||
Execute | BOOL | 立ち上げてコマンド実行 |
ConnectionHdl | DWORD | OPCUA Connection Handle |
MethodHdl | DWORD | Method Connection Handle |
Timeout | TIME | Disconnect Timeout設定 |
VAR_OUTPUT | ||
Done | BOOL | エラーなしで実行OK |
Busy | BOOL | 実行中 |
Error | BOOL | エラーあり |
ErrorID | DWORD | エラーID |
ST_OutputArgInfoAndData
このDUTは自分で定義する必要があります。実際のMethodに合わせてください。
nNumberOfOutputArguments | UDINT | Outputパラメタの数 |
nReserved | ARRAY[1..4] OF BYTE | |
stOutputArgInfo | ARRAY[1..1] OF ST_UAMethodArgInfo | Outputパラメタの数分のST_UAMethodArgInfoが定義します。もしOutputパラメタ3つあれば配列の構造は[1..4]になります。 |
pro | LREAL | 出力データ。実際Methodに合わせてください。 |
MEMSET
この関数を使ってPLC変数をメモリエリア特定し指定な値を一括書き込みます。
VAR_INPUT | ||
destAddr | DWORD | 書き込みしたい変数のStart Memoryアドレス |
fillByte | USINT | 書き込みしたいの値 |
n | UINT | 何Byte書き込みたいのか |
Return | ||
UINT | 0:パラメタエラー、destAddrかnが0になっています。>0:SetされたBytes数が戻り値になります。 |
MEMCPY
この関数を使ってPLC変数をあるエリアから別のMemoryエリアにCopyできます。
VAR_INPUT | ||
destAddr | DWORD | 転送先のStart Memoryアドレス |
srcAddr | USINT | 転送元のStart Memoryアドレス |
n | UINT | 何Byte転送したいのか |
Return | ||
UINT | 0:パラメタエラー、destAddr、srcAddrかnが0になっています。>0:SetされたBytes数が戻り値になります。 |
Code
VAR
最初には変数の定義ですね。UA_Connect、UA_GetNamespaceIndex、UA_Disconnectは前と変わっていません。多くなったのはUA_MethodGetHandle、UA_MethodCall、UA_MethodreleaseHandleの部分で、それならの変数をどう使うかプログラムを説明するときに話しします。
VAR //Connect/DisConnect Functionality UA_CONNENT :UA_CONNECT; UASessionInfo :ST_UASessionConnectInfo; UA_Disconnect :UA_Disconnect; //NameIndex UA_GetNamespaceIndex : UA_GetNamespaceIndex; nNamespaceIndex : UINT; //UA_MethodGetHandle UA_MethodGetHandle : UA_MethodGetHandle; ObjectNodeID : ST_UANodeID; MethodNodeID : ST_UANodeID; nMethodHdl : DWORD; //UA_MethodCall UA_MethodCall : UA_MethodCall; sObjectNodeIdIdentifier : STRING(MAX_STRING_LENGTH) := ‘Demo.Method’; sMethodNodeIdIdentifier : STRING(MAX_STRING_LENGTH) := ‘Demo.Method.Multiply’; nAdrWriteData : PVOID; //Method Parameters numberIn1 : LREAL := 100.0; numberIn2 : LREAL := 31.2; numberOutPro: LREAL; //UA_MethoCall Configs cbWriteData : UDINT; InputArguments : ARRAY[1..2] OF ST_UAMethodArgInfo; // change according to input parameters stOutputArgInfo : ARRAY[1..1] OF ST_UAMethodArgInfo; // change according to output parameters stOutputArgInfoAndData : ST_OutputArgInfoAndData; nInputData : ARRAY[1..16] OF BYTE; // numberDouble(8) + numberIn2(Double)(8) nOffset : UDINT; //UA_MethodreleaseHandle UA_MethodReleaseHandle: UA_MethodReleaseHandle; //Operating Variables iState :INT; iErroStep :INT; bError :BOOL; bDone :BOOL; bBusy :BOOL; bReset :BOOL; bInit :BOOL; nErrorID :DWORD; nConnectionHdi :DWORD; bInputDataError :BOOL; arrTON :ARRAY[0..3]OF TON; END_VAR |
VAR CONSTANT
こちらは定数の定義になります。前と比べると増えたのはMethod CallのStep定義、Method CallのInputArguments数(2個)とOutputArguments数(1個)、あとInputArguments数のMemory Size(Byteで計算、Double2つなので、8×2=16になります。)。
VAR CONSTANT ciStep_Parameters_Initlize :INT :=0; ciStep_UAConnect :INT :=10; ciStep_UAGetNamespaceIndex :INT :=20; ciStep_UAGetMethodHandleInit :INT :=70; ciStep_UAGetMethodHandle :INT :=75; ciStep_UAMethodCall :INT :=80; ciStep_UAMethodRelease :INT :=90; ciStep_UADIsconnect :INT :=100; ciStep_End :INT :=7000; ciStep_UAError :INT :=9000; ctUASession_Connect_Timeout :TIME :=T#1M; ctUASession_Session_Timeout :TIME :=T#1M; ctUAConnect_Timeout :TIME :=T#5S; cstServerURL :STRING :=’opc.tcp://DESKTOP-7FE1JP2:48010′; ciScalar INT :=-1; ciNumberOfInputArguments : UDINT := 2; // change to number of input parameters ciNumberOfOutputArguments : UDINT := 1; // change to number of output parameters ciInputArgSize : INT := 16; ciUAMethodHandleInitMemoryError :DWORD:=16#70AA; END_VAR |
PROGRAM
Step間の制御はCASE文を使っています。
- Flag,Function初期化
- Serverと接続
- NameSpaceIndexHandle取る
- Method パラメタ初期化
- MethodHandle取る
- MethodHandleリリース
- ServerとDisconnect
の流れです。
Blockに実行エラーがあると、エラーCodeを記録しErrorStepに飛び、ResetがされたたらまたFlag,Function初期化から始まります。多分も絵描かなくてもわかると思います。
Method Example
今回は前と同じくOPC Demo Serverを使用します。UAExpertから接続しNodeをみますとDemo>003_Method>MultiplyというMethodがあります。プログラムの例ではこのMethodを呼び出します。
ciStep_Parameters_Initlize
Flag,Function初期化です。
ciStep_Parameters_Initlize: IF bReset THEN bReset:=FALSE; END_IF; bError :=FALSE; bDone :=FALSE; bBusy :=FALSE; bInputDataError:=False; nErrorID:=0; iErroStep:=0; UASessionInfo.tConnectTimeout:=ctUASession_Connect_Timeout; UASessionInfo.tSessionTimeout:=ctUASession_Session_Timeout; UASessionInfo.eSecurityMode:=eUASecurityMsgMode_None; UASessionInfo.eSecurityPolicyUri:=eUASecurityPolicy_None; UASessionInfo.eTransportProfileUri:=eUATransportProfileUri_UATcp; UA_CONNENT( Execute:=FALSE ); UA_Disconnect( Execute:=FALSE ); UA_GetNamespaceIndex( Execute:=FALSE ); UA_MethodGetHandle( Execute:=FALSE ); UA_MethodCall( Execute:=FALSE ); UA_MethodReleaseHandle( Execute:=FALSE ); IF NOT UA_CONNENT.Busy AND NOT UA_Disconnect.Busy AND NOT UA_GetNamespaceIndex.Busy AND NOT UA_MethodGetHandle.Busy AND NOT UA_MethodCall.Busy AND NOT UA_MethodReleaseHandle.Busy THEN iState:=ciStep_UAConnect; END_IF |
ciStep_UAConnect
Serverと接続します。
ciStep_UAConnect: UA_CONNENT( Execute:=TRUE ,ServerUrl:=cstServerURL ,SessionConnectInfo:=UASessionInfo ,Timeout:=ctUAConnect_Timeout ,ConnectionHdl=>nConnectionHdi ); IF UA_CONNENT.Done AND NOT UA_CONNENT.Error THEN UA_CONNENT( Execute:=FALSE ); iState:=ciStep_UAGetNamespaceIndex; ELSIF UA_CONNENT.Error THEN iState:=ciStep_UAError; nErrorID:=UA_CONNENT.ErrorID; iErroStep:=ciStep_UAConnect; END_IF |
ciStep_UAGetNamespaceIndex
NameSpaceIndexHandle取ります。
ciStep_UAGetNamespaceIndex: UA_GetNamespaceIndex( Execute:=TRUE ,ConnectionHdl:=nConnectionHdi ,NamespaceUri:= ‘http://www.unifiedautomation.com/DemoServer/’ ,NamespaceIndex=>nNamespaceIndex ); IF UA_GetNamespaceIndex.Done AND NOT UA_GetNamespaceIndex.Error THEN UA_GetNamespaceIndex( Execute:=FALSE ); iState:=ciStep_UAGetMethodHandleInit; ELSIF UA_GetNamespaceIndex.Error THEN iState:=ciStep_UAError; nErrorID:=UA_GetNamespaceIndex.ErrorID; iErroStep:=ciStep_UAGetNamespaceIndex; END_IF |
ciStep_UAGetMethodHandleInit
sObjectNodeIdIdentifier
Methodを呼び出すにはObjectIDが必要になります。まず003_MethodのFolderをクリックします。Methodを呼び出すにはObjectIDが必要になります。まず003_MethodのFolderをクリックします。
右にあるNodeIdはns=2;s=Demo.Methodの値がありますね。
つまり、Namespace=2,sはStringで定義、その値はDemo.Methodになりますね。
なので、ObjectIDはDemo.Methodです。
もちろん、下にIdentifierの項目から直接Copyしてもいいです。
sMethodNodeIdIdentifier
次はNodeIdですね。003_Methodの下にあるのMultiplyをクリックします。
右にあるNodeIdはns=2;s=Demo.Method.Multiplyの値がありますね。
つまり、Namespace=2,sはStringで定義、その値はDemo.Method.Multiplyになりますね。
なので、MethodIDはDemo.Method.Multiplyです。
もちろん、下にIdentifierの項目から直接Copyしてもいいです。
Input/Output Arguments
各Methodにも自分のInputArgumentsとOutputArgumentsがあります。例えば、MultiplyのInputArgumentsをクリックします。
中にInputArgumentsが何個あるかわかります。
下図の例だと2つですね。Argument Array[2]なので。
Data Type・ValveRank
InputArgumentsのDataTypeにも重要です。Value>DataType>Identifierには下図の例だと11[Double]が示しています。OPCUA のDoubleだとBeckhoffのLREALに対応します。
一覧表は最初にあるSupport Data Typeに一覧があります。
Methodのコンセプトわかった時点で次は実際Initなどはなにかやるかを絵で説明します。
ciStep_UAGetMethodHandleInit: ObjectNodeID.eIdentifierType:=eUAIdentifierType_String; ObjectNodeID.nNamespaceIndex:=nNamespaceIndex; ObjectNodeID.sIdentifier:=sObjectNodeIdIdentifier; MethodNodeID.eIdentifierType:=eUAIdentifierType_String; MethodNodeID.nNamespaceIndex:=nNamespaceIndex; MethodNodeID.sIdentifier:=sMethodNodeIdIdentifier; nOffset:=0; MEMSET( destAddr:=ADR(nInputData) ,fillByte:=0 ,n:=SIZEOF(nInputData) ); InputArguments[1].DataType:=eUAType_Double; InputArguments[1].ValueRank:=ciScalar; InputArguments[1].ArrayDimensions[1]:=0; InputArguments[1].nLenData:=SIZEOF(numberIn1); IF nOffset +SIZEOF(numberIn1) > ciInputArgSize THEN bInputDataError:=TRUE; nErrorID:=ciUAMethodHandleInitMemoryError; iState:=ciStep_UAError; iErroStep:=ciStep_UAGetMethodHandleInit; ELSE MEMCPY( destAddr:=ADR(nInputData)+nOffset ,srcAddr:=ADR(numberIn1) ,n:=SIZEOF(numberIn1) ); nOffset:=nOffset+SIZEOF(numberIn1); END_IF InputArguments[2].DataType:=eUAType_Double; InputArguments[2].ValueRank:=ciScalar; InputArguments[2].ArrayDimensions[2]:=0; InputArguments[2].nLenData:=SIZEOF(numberIn2); IF nOffset+SIZEOF(numberIn2) > ciInputArgSize THEN bInputDataError:=TRUE; nErrorID:=ciUAMethodHandleInitMemoryError; iState:=ciStep_UAError; iErroStep:=ciStep_UAGetMethodHandleInit; ELSE MEMCPY( destAddr:=ADR(nInputData)+nOffset ,srcAddr:=ADR(numberIn2) ,n:=SIZEOF(numberIn2) ); nOffset:=nOffset+SIZEOF(numberIn2); cbWriteData:=nOffset; iState:=ciStep_UAGetMethodHandle; END_IF |
ciStep_UAGetMethodHandle
MethodHandleを取ります。
ciStep_UAGetMethodHandle: UA_MethodGetHandle( Execute:=TRUE ,ConnectionHdl:=nConnectionHdi ,ObjectNodeID:=ObjectNodeID ,MethodNodeID:=MethodNodeID ,MethodHdl=>nMethodHdl ); IF UA_MethodGetHandle.Done AND NOT UA_MethodGetHandle.Error THEN UA_MethodGetHandle( Execute:=FALSE ); iState:=ciStep_UAMethodCall; ELSIF UA_MethodGetHandle.Error THEN iState:=ciStep_UAError; nErrorID:=UA_MethodGetHandle.ErrorID; iErroStep:=ciStep_UAGetMethodHandle; END_IF |
ciStep_UAMethodCall
最初にはMethod出力のMemory Sizeだけ定義しそのあとはUA_MethodCallします。
ciStep_UAMethodCall:
stOutputArgInfo[1].nLenData:=SIZEOF(stOutputArgInfoAndData.pro);
UA_MethodCall(
Execute:=TRUE
,ConnectionHdl:=nConnectionHdi
,MethodHdl:=nMethodHdl
,nNumberOfInputArguments:=ciNumberOfInputArguments
,pInputArginfo:=ADR(InputArguments)
,cbInputArginfo:=SIZEOF(InputArguments)
,pInputArgData:=ADR(nInputData)
,cbInputArgData:=cbWriteData
,pInputWriteData:=0
,cbInputWriteData:=0
,nNumberOfOutputArguments:=ciNumberOfOutputArguments
,pOutputArgInfo:=ADR(stOutputArgInfo)
,cbOutputArgInfo:=SIZEOF(stOutputArgInfo)
,pOutputArgInfoAndData:=ADR(stOutputArgInfoAndData)
,cbOutputArgInfoAndData:=SIZEOF(stOutputArgInfoAndData)
);
IF UA_MethodCall.Done AND NOT UA_MethodCall.Error THEN
UA_MethodCall(
Execute:=FALSE
);
iState:=ciStep_UAMethodCall;
numberOutPro := stOutputArgInfoAndData.pro;
ELSIF UA_MethodCall.Error THEN
iState:=ciStep_UAError;
nErrorID:=UA_MethodCall.ErrorID;
iErroStep:=ciStep_UAMethodCall;
END_IF
ciStep_UAMethodRelease
MethodがCallされたあとリリースします。
ciStep_UAMethodRelease: UA_MethodReleaseHandle( Execute:=TRUE ,ConnectionHdl:=nConnectionHdi ,MethodHdl:=nMethodHdl ); IF UA_MethodReleaseHandle.Done AND NOT UA_MethodReleaseHandle.Error THEN UA_MethodReleaseHandle( Execute:=FALSE ); iState:=ciStep_UAMethodCall; ELSIF UA_MethodReleaseHandle.Error THEN iState:=ciStep_UADIsconnect; nErrorID:=UA_MethodReleaseHandle.ErrorID; iErroStep:=ciStep_UAMethodRelease; END_IF |
ciStep_UADIsconnect
Connectionを切ります。
ciStep_UADIsconnect: UA_Disconnect( Execute:=TRUE ,ConnectionHdl:=nConnectionHdi ); IF UA_Disconnect.Done AND NOT UA_Disconnect.Error THEN iState:=ciStep_End; ELSIF UA_Disconnect.Error THEN iState:=ciStep_UAError; nErrorID:=UA_Disconnect.ErrorID; iErroStep:=ciStep_UADIsconnect; END_IF |
ciStep_End
2秒のDelayのあと再接続します。
ciStep_End: arrTON[0]( IN:=TRUE ,PT:=T#2S ); IF arrton[0].Q THEN arrton[0]( IN:=FALSE ); iState:=ciStep_UAConnect; END_IF |
ciStep_UAError
エラーが来たらこのStepに飛び、エラー待ちになります。
ciStep_UAError:
bError:=TRUE;
IF bReset THEN
iState:=ciStep_Parameters_Initlize;
END_IF;
結果
Method Callされたら、NumberOutProの結果はnumberIn1かけるnumberIn2になります。
プロジェクトは以下のLinkでダウンロードしてください。
はーい、お疲れ様です。
もしなにか質問あれば、メール・コメント・Twitterなどでもどうぞ!
Twitterのご相談:@3threes2
メールのご相談:soup01threes*gmail.com (*を@に)