🍾

OPC UAで公開したファンクションブロックを疑似UA Methodとして使う

に公開

はじめに

本記事は、主にPLC(Programmable Logic Controller)向けのソフトウェア開発に従事、または関心のある方で、Structured Text(ST)による開発に興味がある方向けです。OMRON社のSysmac Studioとコントローラ(NX1またはNX5)を使用します。また、PowerShellを使用します。

今回は、Sysmac Studioのシミュレータ及びNX/NJコントローラのOPC UAサーバが持つ "ユーザ定義ファンクションブロックの変数の OPC UA 通信への公開" 機能(以下、FBインスタンス公開)で公開したファンクションブロック(以下、FB)をクライアントから呼び出し、疑似UA Methodとして使用します。FBインスタンス公開の目的が情報モデルの構造的表現にあるようなので、現状は、ユーザーがUA Methodを定義することができません。しかし、現状のFBインスタンス公開でもクライアント側でUA Methodのような振る舞いを提供することはできます。

疑似UA Methodは、標準的なExecute型FB動作シーケンスをOPC UAを介してネットワーク越しに行うだけです。UA Methodとしての公開であれば、多くのOPC UAスタックが有する機能を使用するだけですが、FBインスタンス公開は、FBの入出力と内部変数のノード公開に留まるのでOPC UAクライアント側で少しコードを組む必要があります。

ネットワーク越しの呼び出しがあまりにも大変であったり、不確実さや制約が強ければやめようと考えていたのですが、そうではなかったので使えるようにしました。私のFBの使用スタイルもありますが、通常のFB使用をネットワーク越しに行うだけなので違和感がありません。ネットワーク越しという一大事はあります。

ユーザーの使い勝手をUA Method風にしたいのであれば、OPC UAクライアント側でメソッド呼び出しのようなシグネチャで使用できるようにします。但し、UA Methodではないのでエラーや意図しない振る舞いは実装依存です。メッセージ交換の実行単位、変数の読み書き順序、FB呼び出し中の切断等について検討する必要があります。

値取得のFB呼び出しは、FB呼び出し中の切断からの復帰方法を考えれば、他はそれほど心配する必要はありません。また、値設定であっても冪等な操作であれば同様です。但し、どのようなFB呼び出しであれ、副作用として即座に装置の状態が変化し、継続的に外部からの制御を必要とするような状態であれば、話は別です。対応が異なるということではなく、そもそもそのような振る舞いとして組んでいることに問題があります。ネットワークの断絶や値の不達によって関連デバイスがエラーモードに移行するようなフィールドバス経由でない限り、常にネットワークは断絶するという前提に立つ必要があります。

Sysmacプロジェクト

以下にSysmacプロジェクトがあります。

https://github.com/kmu2030/PseudoUAMethodExample

公開FB : MtdMyAdd FB

疑似UA Methodの確認用に、MtdMyAddという定義名と以下の構造であるFBを定義します。2つのUINT値を受け取り、その和を出力します。Mtdは、公開FBであることを識別するために適当に決めた定義名の接頭辞です。MtdMyAddの詳細は、Sysmacプロジェクトを参照してください。以下、MtdMyAddをインスタンス化したFBを、常にMyAddとして扱います。

入出力

名称 入出力 データ型
Execute 入力 BOOL
In1 入力 UINT
In2 入力 UINT
Out 出力 UINT
Done 出力 BOOL

コード

ExamplePseudoUAMethod.smc2/POU/ファンクションブロック/MtdMyAdd
IF NOT Execute THEN
    Done := FALSE;
	
    RETURN;
END_IF;

IF Execute AND NOT Done THEN
    Out := In1 + In2;
	
    Done := TRUE;
END_IF;

公開FB実行手順の検討

まず、MyAddをコントローラ内のコードから実行する場合の手順を確認します。このFBを標準的なExecute型FBとして扱うのであれば、以下の手順で使用します。

  1. In1、In2の値を設定
  2. ExecuteをTrueに設定
  3. FBを実行し、DoneがTrueに変化するのを監視
  4. Outの値を取得
  5. Executeの値をFalseに設定してFBを実行

コードで表現すると、以下のようになります。

// 手順1
MyAdd.In1 := 1;
MyAdd.In2 := 1;
// 手順2
MyAdd.Execute := TRUE;
// 手順3
REPEAT
	MyAdd();
UNTIL MyAdd.Done
END_REPEAT;
// 手順4
Out := MyAdd.Out;
// 手順5
MyAdd.Execute := FALSE;
MyAdd();

FBインスタンス公開で公開するFBは、これと同じ手順をOPC UAのノード値読み書きを介して実行すると、FBの呼び出し、疑似UA Methodとして扱えるようになります。より保守的なFB呼び出しにする場合、以下の処理を追加します。

  • Busyを追加し、In1、In2の値を設定する前にBusyがFalseであることを確認
  • Executeの値をFalseに設定した後、DoneがFalseに変化するのを監視

使用するOPC UAクライアントが複数ノード値の読み出しや書き込みを1つの実行単位にまとめる機能を提供していたとしても、それらの実行順序が不確かであれば、1と2、3と4を実行単位として分けます。そうでない場合、1と23と45の3つの実行単位となります。

複数ノード値の読み出しと書き込みをまとめられるとすると、公開FBは、以下のOPC UAクライアントからの操作によって疑似UA Methodとして扱えるようになります。

  1. In1、In2、Execute(True)のノード値書き込み
  2. Done、OutをDoneがTrueになるまでノード値読み出し
  3. Execute(False)のノード値書き込み

FBの処理時間が長いと、2がポーリングのように振る舞います。FBの処理が多サイクルに及ぶ場合や、FBを実行するタスクの実行周期がOPC UAクライアントのメッセージ交換実行間隔に対して大きい場合は対応が必要です。場合によっては、疑似UA Methodの使用は適さないとして、他の交換方法にすることが必要であるかもしれません。

MyAddは、実行するタスクの周期が適当であれば、FBの処理時間がOPC UAクライアントのメッセージ交換実行間隔に対して過度なポーリングになることはありません。

FBインスタンス公開の留意点

FBインスタンス公開には、疑似UA Methodに関連する事項として以下の留意点があります。

  1. FBインスタンス名がノードIDの一部になる
  2. FBインスタンスを実行するPOUの名称がノードIDの一部になる
  3. シミュレータ用OPC UAサーバとコントローラのOPC UAサーバでノードツリー構造が異なる

1と2は、コントローラのプログラム、3はOPC UAクライアントのプログラムに関連します。

1. FBインスタンス名がノードIDの一部になる

OPC UAの"オブジェクト"という思想に沿うのであれば、適当な名称というわけにはいきません。UA Methodに寄せるのであれば、何らかのオブジェクトのメソッド名として妥当な名称にする必要があります。そのため、FBの定義名は、公開する可能性のある名称を避ける必要があります。定義名に適当なプレフィックスやサフィックスを付けることにすれば問題ありません。

2. FBインスタンスを実行するPOUの名称がノードIDの一部になる

FBインスタンス名がノードIDの一部になることと入れ子のFBを考えれば難しくはありません。適切な入れ子のFBを定義することで、OPC UAの"オブジェクト"に対する"クラス"を仕立てることもできます。"クラス"とはいっても極限定的な見方においてです。

MyAddを持つClsMathというFBを定義します。内容は、以下です。

ExamplePseudoUAMethod.smc2/POU/ファンクションブロック/ClsMath
IF NOT Enable THEN
    MyAdd.Execute := FALSE;
    Busy := FALSE;
	
    RETURN;
END_IF;

IF Enable AND Busy THEN
    MyAdd();
END_IF;

これをMathという名称のインスタンスとしてMethodsという名称のプログラムで実行すると、MyAddは、コントローラのOPC UAサーバで以下のノードIDを持ちます。

ns=4;s=Methods/Math/MyAdd

階層構造が、即ちオブジェクトとそのメソッドを意味するわけではありませんが、何らかの関係性があることは見てとれます。現在のFBインスタンス公開の意図が、コンパニオン仕様としての情報モデルの取り扱いにあり、その構造的表現が目的のようなのでこのようになります。オブジェクトのメンバーがプロパティなのかメソッドなのかは、予め使用者ないしスタックが認識するので支障は無いと思います。

3. シミュレータとコントローラでノードツリー構造が異なる

シミュレータ用OPC UAサーバとコントローラのOPC UAサーバでは、公開されるノードツリー構造が異なります。疑似UA Methodに関連する差異は、ノードIDのネームスペースインデックス、階層セパレータ、公開FBを含むプログラムPOUの親についてです。適当なGUIのOPC UAクライアントを使用すると簡単にノードツリー全体を確認できます。

MyAddMethodsというプログラムPOUに配置して公開すると、ノードIDは、シミュレータ用OPC UAサーバとコントローラのOPC UAサーバで以下のようになります。

シミュレータ用OPC UAサーバでのノードID:

ns=2;s=Programs.Methods.MyAdd

コントローラのOPC UAサーバでのノードID:

ns=4;s=Methods/MyAdd

ノードIDの差異は、以下です。

OPC UAサーバ ネームスペースインデックス プログラムPOUの親 階層セパレータ
シミュレータ 2 Programs .
コントローラ 4 /

どちらのOPC UAサーバに対しても使用する可能性のあるアプリケーションは、これらの差異を吸収できるようにする必要があります。好ましい仕様ではありませんが、このことを確認していれば問題ありません。

公開FBの呼び出し

公開FBの呼び出しは、クライアント側の内容なので、MyAddを呼び出すクライアントアプリケーションを確認します。OPC UAクライアントは、PwshOpcUaClientを使用します。PwshOpUaClientUA.NETStandardをPowerShellで使用するだけなので、C#でも同等のコードになります。また、操作内容が同等であれば、他の処理系やスタックでも実装可能です。但し、スタックのメッセージ交換の実装によっては、手順を修正する必要があります。

クライアントアプリケーションの構成

MyAddを呼び出すクライアントアプリケーションは、以下で構成します。

  • PwshOpcUaClient/
    PwshOpcUaClientです。
    使用方法は、PwshOpcUaClientを参照します。

  • ExamplePseudoUAMethod.ps1  
    PwshOpcUaClientを使用してMyAddの呼び出しを行うPowerShellスクリプトです。

  • ExamplePseudoUAMethod.smc2  
    OPC UAサーバ側のプログラムです。
    コントローラ、Sysmac Studioのシミュレータで実行します。

アプリケーション

アプリケーション本体は、ExamplePseudoUAMethod.ps1です。Call-MyAdd関数がMyAddを呼び出す処理です。

ExamplePseudoUAMethod.ps1
using namespace Opc.Ua
param(
    [bool]$UseSimulator = $true,
    [string]$ServerUrl = 'opc.tcp://localhost:4840',
    [bool]$UseSecurity = $true,
    [string]$UserName = 'taker',
    [string]$UserPassword = 'chocolatepancakes',
    [double]$Interval = 0.05
)
. "$PSScriptRoot/PwshOpcUaClient/PwshOpcUaClient.ps1"

function Main () {
    try {
        $AccessUserIdentity = [string]::IsNullOrEmpty($UserName) `
                                ? (New-Object UserIdentity) `
                                : (New-Object UserIdentity -ArgumentList $UserName, $UserPassword)
        $clientParam = @{
            ServerUrl = $ServerUrl
            UseSecurity = $UseSecurity
            SessionLifeTime = 60000
            AccessUserIdentity = $AccessUserIdentity
        }
        $client = New-PwshOpcUaClient @clientParam

        $in1 = 0
        $in2 = 1000
        While ($true) {
            $result = Call-MyAdd -Session $client.Session -In1 $in1 -In2 $in2
            "MyAdd($in1, $in2) = $result"
                | Write-Host

            ++$in1
            if ($in1 -gt 100) { break }
            Start-Sleep -Seconds $Interval
        }
    }
    catch {
        $_.Exception
    }
    finally {
        Dispose-PwsOpcUaClient -Client $client
    }
} 

function Call-MyAdd() {
    param(
        [ISession]$Session,
        [UInt16]$In1,
        [UInt16]$In2
    )

    $baseNodeId = $UseSimulator `
                    ? 'ns=2;s=Programs.Methods.MyAdd'
                    : 'ns=4;s=Methods/MyAdd'
    $separator = $UseSimulator ? '.' : '/'
  
    # Create call method parameters.
    $callParams = New-Object WriteValueCollection
    $callParam = New-Object WriteValue
    $callParam.NodeId = New-Object NodeId -ArgumentList (@($baseNodeId, 'In1') -join $separator)
    $callParam.AttributeId = [Attributes]::Value
    $callParam.Value = New-Object DataValue
    $callParam.Value.Value = $In1
    $callParams.Add($callParam)
    $callParam = New-Object WriteValue
    $callParam.NodeId = New-Object NodeId -ArgumentList (@($baseNodeId, 'In2') -join $separator)
    $callParam.AttributeId = [Attributes]::Value
    $callParam.Value = New-Object DataValue
    $callParam.Value.Value = $In2
    $callParams.Add($callParam)
    $callParam= New-Object WriteValue
    $callParam.NodeId = New-Object NodeId -ArgumentList (@($baseNodeId, 'Execute') -join $separator)
    $callParam.AttributeId = [Attributes]::Value
    $callParam.Value = New-Object DataValue
    $callParam.Value.Value = [bool]$true
    $callParams.Add($callParam)

    $results = $null
    $diagnosticInfos = $null
    $response = $Session.Write(
        $null,
        $callParams,
        [ref]$results,
        [ref]$diagnosticInfos
    )
    if ($null -ne ($exception = ValidateResponse `
                                    $response `
                                    $results `
                                    $diagnosticInfos `
                                    $callParams `
                                    'Failed to execute.')
    ) {
        throw $exception
    }

    # Create done call parameters.
    $doneParams = New-Object ReadValueIdCollection
    $doneParam = New-Object ReadValueId -Property @{
        AttributeId = [Attributes]::Value
    }
    $doneParam.NodeId = New-Object NodeId -ArgumentList (@($baseNodeId, 'Done') -join $separator)
    $doneParams.Add($doneParam)

    $doneParam= New-Object ReadValueId -Property @{
        AttributeId = [Attributes]::Value
    }
    $doneParam.NodeId = New-Object NodeId -ArgumentList (@($baseNodeId, 'Out') -join $separator)
    $doneParams.Add($doneParam)

    $results= New-Object DataValueCollection
    $diagnosticInfos = New-Object DiagnosticInfoCollection
    do {
        $response = $Session.Read(
            $null,
            [double]0,
            [TimestampsToReturn]::Both,
            $doneParams,
            [ref]$results,
            [ref]$diagnosticInfos
        )
        if ($null -ne ($exception = ValidateResponse `
                                        $response `
                                        $results `
                                        $diagnosticInfos `
                                        $doneParams `
                                        'Failed to done.')
        ) {
            throw $exception
        }
    }
    until ($results.Count -gt 0 -and $results[0].Value)

    $retVal = $results[1].Value

    $clearParams = New-Object WriteValueCollection
    $clearParam = New-Object WriteValue
    $clearParam.NodeId = New-Object NodeId -ArgumentList (@($baseNodeId, 'Execute') -join $separator)
    $clearParam.AttributeId = [Attributes]::Value
    $clearParam.Value = New-Object DataValue
    $clearParam.Value.Value = [bool]$false
    $clearParams.Add($clearParam)
  
    $results = $null
    $diagnosticInfos = $null
    $Session.Write(
        $null,
        $clearParams,
        [ref]$results,
        [ref]$diagnosticInfos
    ) | Out-Null
    if ($null -ne ($exception = ValidateResponse `
                                    $response `
                                    $results `
                                    $diagnosticInfos `
                                    $clearParams `
                                    'Failed to clear.')
    ) {
        throw $exception
    }
    
    return $retVal
}

class OpcUaFetchException : System.Exception {
    [hashtable]$CallInfo
    OpcUaFetchException([string]$Message, [hashtable]$CallInfo) : base($Message)
    {
        $this.CallInfo = $CallInfo
    }
}

function ValidateResponse {
    param(
        $Response,
        $Results,
        $DiagnosticInfos,
        $Requests,
        $ExceptionMessage
    )

    if (($Results
            | Where-Object { $_ -is [StatusCode]}
            | ForEach-Object { [ServiceResult]::IsNotGood($_) }
        ) -contains $true `
        -or ($Results.Count -ne $Requests.Count)
    ) {
        return [OpcUaFetchException]::new($ExceptionMessage, @{
            Response = $Response
            Results = $Results
            DiagnosticInfos = $DiagnosticInfos
        })
    } else {
        return $null
    }
}

Main

Call-MyAdd関数

MyAddの呼び出しは、Call-MyAdd関数で行います。MyAddは標準的なExecute型FBなので、コントローラ内のプログラムでそれを呼び出すように、ノードの読み書きをします。FBの入出力は、FBインスタンス公開によってFBインスタンスノード下のノードとして公開されます。

MyAddMethodsで実行するコントローラのOPC UAサーバで、入出力は、以下のノードとして公開されます。いずれも変数ノード(NodeClass=2)です。

入出力 ノードID
Execute ns=4;s=Methods/MyAdd/Execute
In1 ns=4;s=Methods/MyAdd/In1
In2 ns=4;s=Methods/MyAdd/In2
Out ns=4;s=Methods/MyAdd/Out
Done ns=4;s=Methods/MyAdd/Done

公開FB呼び出しを多用する場合、これらをハードコードすることは現実的ではありません。クライアントでノードツリーからディスカバリーできるようにするか、オブジェクト(ここでは、MyAdd)のノード情報から必要なノード情報を生成するフレームを作成します。オブジェクトに対応したクラスの定義がお手軽です。

1. In1、In2、Execute(True)のノード値書き込み

まずは、MyAddの入力変数への書き込みを行います。このとき、**Executeは最後に書き込みます。**より保守的に実装するのであれば、Executeとそれ以外の書き込み処理を分けます。

    # Create call method parameters.
    $callParams = New-Object WriteValueCollection
    $callParam = New-Object WriteValue
    $callParam.NodeId = New-Object NodeId -ArgumentList (@($baseNodeId, 'In1') -join $separator)
    $callParam.AttributeId = [Attributes]::Value
    $callParam.Value = New-Object DataValue
    $callParam.Value.Value = $In1
    $callParams.Add($callParam)
    $callParam = New-Object WriteValue
    $callParam.NodeId = New-Object NodeId -ArgumentList (@($baseNodeId, 'In2') -join $separator)
    $callParam.AttributeId = [Attributes]::Value
    $callParam.Value = New-Object DataValue
    $callParam.Value.Value = $In2
    $callParams.Add($callParam)
    $callParam= New-Object WriteValue
    $callParam.NodeId = New-Object NodeId -ArgumentList (@($baseNodeId, 'Execute') -join $separator)
    $callParam.AttributeId = [Attributes]::Value
    $callParam.Value = New-Object DataValue
    $callParam.Value.Value = [bool]$true
    $callParams.Add($callParam)

    $results = $null
    $diagnosticInfos = $null
    $response = $Session.Write(
        $null,
        $callParams,
        [ref]$results,
        [ref]$diagnosticInfos
    )
    if ($null -ne ($exception = ValidateResponse `
                                    $response `
                                    $results `
                                    $diagnosticInfos `
                                    $callParams `
                                    'Failed to execute.')
    ) {
        throw $exception
    }  

ノード値の書き込みは、UA.NETStandardのそれです。MyAddの入力変数は、プリミティブ型の値だけなので書き込む値の設定が素直です。FBが入力として構造体を必要とすると、構造体に対応したオブジェクトをUA.NETStandardのComplexTypeSystemで型情報を取得して生成、使用することになります。

2. Done、OutをDoneがTrueになるまでノード値読み出し

次に、FBの呼び出し結果を取得します。そのためには、出力変数の読み出しを行います。このとき、**Doneを最初に読み出します。**より保守的に実装するのであれば、Doneとそれ以外の読み出し処理を分けます。

    # Create done call parameters.
    $doneParams = New-Object ReadValueIdCollection
    $doneParam = New-Object ReadValueId -Property @{
        AttributeId = [Attributes]::Value
    }
    $doneParam.NodeId = New-Object NodeId -ArgumentList (@($baseNodeId, 'Done') -join $separator)
    $doneParams.Add($doneParam)

    $doneParam= New-Object ReadValueId -Property @{
        AttributeId = [Attributes]::Value
    }
    $doneParam.NodeId = New-Object NodeId -ArgumentList (@($baseNodeId, 'Out') -join $separator)
    $doneParams.Add($doneParam)

    $results= New-Object DataValueCollection
    $diagnosticInfos = New-Object DiagnosticInfoCollection
    do {
        $response = $Session.Read(
            $null,
            [double]0,
            [TimestampsToReturn]::Both,
            $doneParams,
            [ref]$results,
            [ref]$diagnosticInfos
        )
        if ($null -ne ($exception = ValidateResponse `
                                        $response `
                                        $results `
                                        $diagnosticInfos `
                                        $doneParams `
                                        'Failed to done.')
        ) {
            throw $exception
        }
    }
    until ($results.Count -gt 0 -and $results[0].Value)

    $retVal = $results[1].Value

ノード値の読み出しは、ノード値の書き込みに比べると簡素です。出力変数に構造体がある場合、UA.NETStandardのComplexTypeSystemが、適当なオブジェクトとして解釈します。

3. Execute(False)のノード値書き込み

最後に、FB呼び出しのフラグであるExecuteをFalseにします。Execute変数への書き込みです。MyAddの入力変数への書き込みと同等です。

    $clearParams = New-Object WriteValueCollection
    $clearParam = New-Object WriteValue
    $clearParam.NodeId = New-Object NodeId -ArgumentList (@($baseNodeId, 'Execute') -join $separator)
    $clearParam.AttributeId = [Attributes]::Value
    $clearParam.Value = New-Object DataValue
    $clearParam.Value.Value = [bool]$false
    $clearParams.Add($clearParam)

    $results = $null
    $diagnosticInfos = $null
    $Session.Write(
        $null,
        $clearParams,
        [ref]$results,
        [ref]$diagnosticInfos
    ) | Out-Null
    if ($null -ne ($exception = ValidateResponse `
                                    $response `
                                    $results `
                                    $diagnosticInfos `
                                    $clearParams `
                                    'Failed to clear.')
    ) {
        throw $exception
    }

これらの標準的なExecute型FBの呼び出し処理は、入力及び出力変数はFBによって異なりますが、手順は同じです。そのため、使いまわしのできる関数として切り出すこともできます。疑似UA Methodは、少なくとも3回のメッセージ交換を行うことになるので、本来のUA Methodに比べると非効率なはずです。

公開FBを実際に呼び出す

Sysmacプロジェクトをシミュレータ、コントローラで動作させ、PowerShellスクリプトを実行してMyAddを呼び出します。

動作環境

以下の環境で動作確認をしています。

対象 バージョン
コントローラ NX102-9000 Ver.1.64 HW Rev.A
Sysmac Studio Ver.1.63
PowerShell 7.5.2

FBインスタンス公開は、新しい機能なのでコントローラで動作させる場合、以下を満たす必要があります。

コントローラ バージョン
NJ501-1x00 Ver.1.62以降
NX102-xxxx Ver.1.64以降
NX502-1x00 Ver.1.64以降
NX701-1xxx Ver.1.34以降

共通事項

PowerShellスクリプトを実行すると、サーバとのセッション確立時や、メッセージ交換での署名または、署名と暗号化の使用でサーバ、クライアントそれぞれが証明書を拒否する可能性があります。いずれも拒否した証明書を信頼することで、次回の接続から拒否しなくなります。証明書に問題が無ければ、以下の操作を行います。

  • PwshOpcUaClientがサーバ証明書を拒否したとき
    サーバ証明書が意図したものであれば、PwshOpcUaClient/pki/rejected/certsに格納されたサーバ証明書をPwshOpcUaClient/pki/trusted/certsに移動します。

  • サーバがクライアント証明書を拒否したとき
    コントローラ、シミュレータのOPC UAサーバで拒否した証明書を信頼します。

PowerShellスクリプトは、デフォルトで以下のユーザーとパスワードでOPC UAサーバへの接続を試みます。

ユーザー パスワード
taker chocolatepancakes

シミュレータで確認

シミュレータでの確認手順は、以下です。

  1. ./PwshOpcUaClient/setup.ps1を実行
    PwshOpcUaClientをセットアップします。

  2. Sysmac StuidoでSysmacプロジェクト(ExamplePseudoUAMethod.smc2)を開きシミュレータを起動

  3. シミュレータ用OPC UAサーバの起動と設定
    以下のように操作してOPC UAサーバを起動、設定します。必ず、シミュレータ用OPC UAサーバの公開変数選択公開する変数で**Methods**POUにチェックを入れます。

    シミュレータ用OPC UAサーバの設定

  4. ExamplePseudoUAMethod.ps1を実行
    OPC UAサーバが起動した状態で、以下をPowerShellにて実行します。

    ./ExamplePseudoUAMethod.ps1 -Interval 0.01
    

    OPC UAサーバに接続すると以下のように公開FBであるMyAddを呼び出します。標準的なExecute型FBがそうであるように、処理フラグ(Execute、(Busy)、Done)を確認するため意図しない値が返ってくることはありません。

    シミュレータ用OPC UAサーバのMyAdd呼び出し

コントローラで確認

コントローラでの確認手順は、以下です。

  1. ./PwshOpcUaClient/setup.ps1を実行
    PwshOpcUaClientをセットアップします。

  2. Sysmac StuidoでSysmacプロジェクト(ExamplePseudoUAMethod.smc2)を開き構成を使用環境に合わせる
    コントローラ型式、内蔵EtherNet/IPポート設定を使用環境に合わせます。

  3. Sysmacプロジェクトをコントローラに転送

  4. コントローラのOPC UAサーバの設定  
    Sysmac Studioでコントローラに接続し、以下のように操作してOPC UAサーバを設定します。セキュリティ設定を行ったら、PwshOpcUaClientのクライアント証明書を信頼するために一度アクセスして拒否させた後、信頼リストに移動しています。

    コントローラのOPC UAサーバの設定

  5. ExamplePseudoUAMethod.ps1の実行  
    以下のYOUR_DEVICE_ADDRをコントローラのアドレスに置き換え、PowerShellで実行します。

    ./ExamplePseudoUAMethod.ps1 -UseSimulator $false -ServerUrl YOUR_DEVICE_ADDR -Interval 0.01
    

    OPC UAサーバに接続すると以下のように公開FBであるMyAddを呼び出します。シミュレータに比べ、メッセージ交換回数は少ないはずです。スクリプトを修正してDone値の取得処理回数をカウントして比較することもできます。

    コントローラのOPC UAサーバのMyAdd呼び出し

まとめ

FBを公開できるようになったとき、その対象からしてUA Methodだろうという期待を抱いたのですが、そうではありませんでした。しかし、クライアント側で少し仕事をすれば疑似UA Methodにすることはできます。疑似UA Methodは、FBをリモートに呼び出すという仕組みを、OPC UAプロトコルの上のレイヤーに独自に定義したに過ぎません。必要であれば、EtherNet/IP、FINS、ModbusTCP経由でも可能なことです。しかし、敢えてそのようなことを行う必要性は見受けられません。

OPC UAは、UA Methodという仕組みが存在し、そのような仕組みを含め検討されている規格です。それに類似したものとしてFBインスタンス公開で公開したFBを疑似UA Methodとすることで、同規格の思想において活きる可能性があるためです。過剰に展開すると後々UA Methodが使用できるようになった時の修正が増えます。

次回は、FBインスタンス公開についてもう少し実用的な使用方法を確認します。いわゆる情報モデルやコンパニオン仕様についてではありません。OPC UAサーバ機能について、ユーザーにそこまで機能が提供されている訳でもなければ、コンパニオン仕様は共通化されたものを使用することで恩恵を受けられるからです。OPC UAについての取り組みがさらに広まれば、それらも一般的な要件になるかもしれません。

FBインスタンス公開は、特定のコンパニオン仕様にユーザーが対応する手段として提供されているものと思われますが、特定の使用目的や用途が示されている訳ではありません。また、今のところOPC UAとしてもPOU定義の階層構造に応じた変数を公開するに留まります。

FBインスタンス公開は、OPC UAサーバ機能の一つとして提供されています。そして、OPC UAサーバの持つセキュリティ機能は、ユーザーが頑張って構築できるものではないので利用価値があります。また、どのような使用方法であってもOPC UAプロトコルの操作によってなされるので、クライアント側も適当なOPC UAスタックを使用することで、細々とした取り決めをせずに済みます。

これらをもって実用的とし、公開FBと、そのFBを使用するクライアントの両側からFBインスタンス公開を使用します。OPC UAサーバに少し無理をさせたいので、RingBufferを対象に公開FBと、それを使用するクライアントを作成して確認します。FBインスタンス公開の元来の目的である情報モデルの構造的表現としては、素朴なオブジェクトの公開に留まります。

Discussion