今回の記事ではBerghof MC-PIコントローラーとWAGOのPFC100でOPC UA PubSub通信を使用し、データ交換を行います。
さ、FAを楽しもう。

Reference Link
前書き
いつも私の技術ブログとYouTubeチャンネルをご覧いただき、心より感謝申し上げます。また、いまFullさん(full@桜 八重 (@fulhause) / X)と共に毎週水曜日の夜にお届けしている「高橋クリス」ラジオ番組を運営しています。
技術は独り占めせず、届けるもの
私たちは工場の生産技術や制御に関する技術情報を、ブログや動画などで無料公開しています。「知識は誰でもアクセスできるべき」という信念のもと、現場で役立つ具体的なノウハウやトラブル事例などを発信してきました。すべて無料で続けているのは、「知らなかったせいで困る人」を少しでも減らしたいからです。
また、もしあなたの現場で…
- 「このPLCとデバイスの組み合わせ、ちゃんと動くのかな?」
- 「EtherCAT通信でうまくいかない部分を検証してほしい」
- 「新しいリモートI/Oを試したいけど社内に検証環境がない」
など、困っている構成や試してみたいアイデアがあれば、ぜひお知らせください。機器の貸出や構成の共有が可能であれば、検証し、記事や動画で発信します(ご希望に応じて匿名対応も可能です)。
支援のかたち
現在、私達の活動はほぼ無償で続けており、記事や動画の制作には、時間と検証環境の整備が必要です。この活動を継続的にコンテンツを提供するためには、皆様の温かいご支援が大変重要です。
メンバーシップ(ラジオの応援)
Fullさんとのラジオをより充実させるための支援プランです。
https://note.com/fulhause/membership/join
Amazonギフトリスト
コンテンツ制作に必要な機材・書籍をリストにしています。
https://www.amazon.co.jp/hz/wishlist/ls/H7W3RRD7C5QG?ref_=wl_share
Patreon(ブログ・動画活動への応援)
月額での小さなご支援が、記事の執筆・検証環境の充実につながります。
https://www.patreon.com/user?u=84249391
Paypal
小さな支援が大きな力になります。
https://paypal.me/soup01threes?country.x=JP&locale.x=ja_JP
知ってたら助かること、届けたいだけです
あなたの応援が、知識の共有をもっと自由で持続可能なものにしてくれます。これからもどうぞよろしくお願いします。
soup01threes*gmail.com
技術はひとりじゃもったいない。
CODESYS OPC UA PubSub
Codesys OPC UA PubSub SLライブラリは、OPC UA Foundationによって定義されたPub/Subプロトコルによるメッセージ交換を可能にします。
OPC UA Pub/Subプロトコルを介した通信は、クライアント/サーバー通信に加えて、OPC UA Foundationの規定に準拠したネットワーク加入者間でのデータ交換の可能性を提供します。注意するのは、データ(DataSet)の構造は自由に定義でき、送り手と受け手の間で事前に合意されます。
データ転送はOPC UA PubSub SLライブラリの助けを借りて、UADP用に定義されたルールに従ってUDP/IP経由で行われます。
パブリッシャーは、そのデータを未知の数のサブスクライバーに公開する。つまり、送信者と受信者はお互いを知らない。そのため、受信者の数も送信者には影響しない。データはOPC UA Foundationの規則に従ってバイナリ形式で転送されます。OPC UA PubSub SLライブラリは、IECデータ型から対応するOPC UAデータ型への変換を行います。これにより、実装は以下のプロファイルに従います:
- Publisher: PubSub Publisher UADP定期固定設定
- Subscriber:PubSub Subscriber UADP 定期固定設定
メッセージの長さは 1500 バイト(MTU)に制限されている(Chunked NetworkMessages はサポートされていない)。Time Sensitive Networkのルールがまだ利用できない限り、ハード・リアルタイム条件には準拠できません。しかし、OPC UA PubSub SL ライブラリの実装では、ジッターを可能な限り低く保とうとします。
Introducing
Publish-Subscribe規格は、OPC UA仕様の14番目の部分です。この規格は、OPC UAが自動化の解決と深化のために採用可能な状況を拡張するために設計されました。OPCファウンデーションによれば、PubSubは2つの重要な方法でOPC UAの統合を可能にします。
PubSubモデルのOPC UAの場合、通信はコネクションレスで未確認です。PubSubはPublish and Subscribeの略で、サーバーがデータをネットワークに送信(Publish)し、すべてのクライアントがそのデータを受信(Subscribe)できるようにします。
データを暗号化したり復号化したりするためには、両方の通信パートナーが 同じセキュリティ認証情報を持っていなければならない。1つのパブリッシャーがデータを提供し、それをネットワーク上の任意の数のサブスクライバーが受信するか、または多数のパブリッシャーが1つのサブスクライバーに情報を送信します。このため、OPC UA PubSubは特にIoTの直接通信やフィールドレベルでの高速サイクリック処理に適しています。
Configuration
このファンクションブロックは、OPC UAアプリケーションのPub/Subコンフィギュレーションを表現するために使用されます。必要に応じてRootDiagnosticsインスタンスを作成し、itfRootDiagnosticsに接続します。これは、itfDiagnostics出力を通してOPC UAアプリケーションの診断データを提供します。
Connection
このファンクション・ブロックはパブ・サブ接続を表す。パブ・サブ接続はプロトコル選択、プロトコル設定、アドレス情報の組み合わせである。
DataSet
DataSetは変数のリストだとイメージしてください。publishingのために、データセットはDataSetMessageにエンコードされる。
CODESYS Forgeのエリアには、2つの異なるDataSetの設計例があります。DataSetは、変数の単純なリスト、または変数グループのいくつかのバリエーションを持つ複雑な構造にすることができます。
Reader
このファンクションブロックは、データセットリーダパラメータを表すために使用されます。必要に応じて、ReaderDiagnostics インスタンスを作成し、itfReaderDiagnostics に接続してください。 itfDiagnostics出力を通してデータセットリーダーの診断データを提供します。
ReaderGroup
このファンクションブロックは、リーダーグループの設定パラメータを表すために使用される。必要に応じて、ReaderGroupDiagnostics インスタンスを作成し、itfReaderGroupDiagnostics に接続してください。 itfDiagnostics出力を通してリーダーグループの診断データを提供します。
Writer
このファンクション・ブロックは、データ・セット・ライタ・パラメータを表すために使用される。
必要に応じて WriterDiagnostics インスタンスを作成し、それを itfWriterDiagnostics に接続してください。 これは itfDiagnostics 出力を通してデータセットライターの診断データを提供します。
WriterGroup
このファンクションブロックは、WriterGroupの設定パラメータを表すために使用される。
必要に応じて WriterGroupDiagnostics インスタンスを作成し、それを itfWriterGroupDiagnostics に接続してください。 これは itfDiagnostics 出力を通してライターグループの診断データを提供します。
METHOD FINAL SetInitialValue : ERROR
SetInititalValueは、コンパイラの初期化シーケンスによって、宣言のコンテキストで静的初期化が正しく動作しない場合のヘルパー・メソッドです。
Implementation
Install Library
最初にCODESYSのOPC UA PubSubライブラリをインストールするために、Tools>CODESYS Installerをクリックします。
Codesys Installer画面が表示され、OPCを検索し、Pubsubライブラリを選択>Installします。
OK進みます。
ライセンスに同意し、Continueで進みます。
ちなみに、OPC UA PubSubのサンプルプロジェクトは下記のDirectoryに格納されています。
POUs Configuration
今回の記事では2つのCPUにも同じプロジェクトで作成しますので、POUs Tabを開き、共通のパラメータや構造体などを定義します。
Add Library
Library Managerをクリックし、ライブラリをImportします。
Ipv4を検索し、Net Base ServicesライブラリをImportします。
次はOPC UA PubSub SLライブラリもインストールしていきましょう。
DUT
POUsに構造体を作成します。
SensorDataStruct
こちらは今回記事で使用した2つのCodesysコントローラー(Berghof社のMC-PIとWAO社のPFC100)がOPC UA PubSubで交換するデータ構造です。
TYPE SensorDataStruct : STRUCT rData1 : REAL; rData2 : REAL; rData3 :REAL; rData4 :REAL; location : STRING(25); END_STRUCT END_TYPE |
GVL
GlobalIP
こちらのGVLはWAGO社のPFC100とBerghofのIPアドレス・そしてマルチキャストアドレス・OPC UA のポートを指定しました。
{attribute ‘qualified_only’} VAR_GLOBAL // the address of the Receiver (Device_1) ipAddressSupervisor : NBS.IPv4Address := (ipAddress:=’192.168.0.40′); // the address of the Sensor (Device_2) ipAddressSensor : NBS.IPv4Address := (ipAddress:=’192.168.0.124′); // the multicast address and port used multicastIpReceiver : NBS.IPv4Address := (ipAddress:=’224.0.8.25′); uiPort : UINT := 4840; END_VAR |

FB
次はFunction Blockを作成します。
SensorDataSet
プロジェクトの一番上を右クリック>Add Object>POUします。
TypeをFunction blockに設定します。
ExtendsのOptionを入れ、隣にある…ボタンをクリックします。
DataSetというFBを検索します。
これでOKで、AddボタンをクリックしFBを追加しましょう。
VAR
FBのVAR部分にPubしくはSubしたい変数のデータタイプを定義します。
{attribute ‘no_assign’} {attribute ‘call_after_init’} {attribute ‘enable_dynamic_creation’} FUNCTION_BLOCK ABSTRACT SensorDataSet EXTENDS UADP.DataSet VAR // this array describes the structure of the data set _aIndex : ARRAY[0..4] OF UADP.Index := [ (* 0 *) (eType:=UADP.IEC_REAL), (* 1 *) (eType:=UADP.IEC_REAL), (* 2 *) (eType:=UADP.IEC_STRING, udiSize:=UADP.STRING_SIZE_TO_UALEN(udiSize:=SIZEOF(SensorDataStruct.location))), (* 3 *) (eType:=UADP.IEC_REAL), (* 4 *) (eType:=UADP.IEC_REAL) ]; END_VAR super^(); |

Method -Init
Init Methodは基本は変更なしでOkです。
{attribute ‘call_after_init’} METHOD PROTECTED Init VAR_INPUT END_VAR // do not change this _pIndex := ADR(_aIndex); _udiIndexSize := SIZEOF(_aIndex) / SIZEOF(UADP.Index); // the version of the data set _Version.udiMajorVersion := UADP.DT_TO_UAVersionTime(DT#2020-01-22-13:42:55.123); _Version.udiMinorVersion := _Version.udiMajorVersion; SUPER^.Init(); |
Berghof Side
最初にBerghof側のMC-PI コントローラーから構築します。今回の記事ではMC-PIコントローラーはSubscribersの役割です。
GVL
OPC UA Subscribe用のGVLを作成します。
GVL_ReceiveData
先ほどPOUで定義した構造体を利用し変数を定義します。
{attribute ‘qualified_only’} VAR_GLOBAL udtData:SensorDataStruct; END_VAR |
FB
rxSensorDataSet
次はOPC UA Subscribe用のFBを作成します。
ioDataはFBの外部から変数を渡せるように設置したパラメータです。
{attribute ‘call_after_init’} FUNCTION_BLOCK rxSensorDataSet EXTENDS SensorDataSet VAR_INPUT END_VAR VAR_OUTPUT END_VAR VAR END_VAR VAR_IN_OUT ioData :SensorDataStruct; END_VAR SUPER^(); |
Method-PrepareValues
PrepareValuesは各Indexの変数のアドレス番地を転送するプログラムです。
// spread the received data over the application _aIndex[0].pValue:=ADR(ioData.rData1); _aIndex[1].pValue:=ADR(ioData.rData2); _aIndex[2].pValue:=ADR(ioData.location); _aIndex[3].pValue:=ADR(ioData.rData3); _aIndex[4].pValue:=ADR(ioData.rData4); PrepareValues := UADP.ERROR.NO_ERROR; |
Program-Communication_PRG
それでは実際にPubsubの通信プログラムを作成しましょう。
VAR
VAR領域でプログラムに必要な変数を定義します。
PROGRAM Communication_PRG VAR // enable the communication xEnable : BOOL; rootDiagnostic : UADP.RootDiagnostics; configuration : UADP.Configuration := (itfRootDiagnostics:=rootDiagnostic); connectionDiagnostics : UADP.ConnectionDiagnostics; connection : UADP.Connection := (itfSubscriberIPSet:=GlobalIP.ipAddressSupervisor, uiPort:=GlobalIP.uiPort, itfIPAddress:=GlobalIP.ipAddressSupervisor, itfConnectionDiagnostics:=connectionDiagnostics, uiPort:=GlobalIP.uiPort, itfSubscriberIPSet:=GlobalIP.multicastIpReceiver, uiPublisherId:=41); readerGroupDiagnostics : UADP.ReaderGroupDiagnostics; readerGroup : UADP.ReaderGroup := (itfReaderGroupDiagnostics:=readerGroupDiagnostics); readerDiagnostics : UADP.ReaderDiagnostics; reader : UADP.Reader := (itfReaderDiagnostics:=readerDiagnostics); readerDataSet : rxSensorDataSet; ftrig : F_TRIG; rtrig : R_TRIG; END_VAR |
FLOW
こちらは通信プログラムの全体的なFLOWになります。
流れとしては設定→接続→Readerなどの設定→通信スタート→周期実行メソットをTRUEになります。
また、ConfigurationなどのFBにはConfigurationのボタンがあります。
このボタンをクリックすると、該当する FBのパラメータ設定を割り当てることができます。
それはConfigurationのiftRootDiagnosticsが初期でrootDiagnosticに割り当てられたからです。
Configuration
最初にConfiguration Blockを設定します。
パラメータ画面上ではすでに診断用の変数と割り当てています。
そしてConfiguration FB Blockの出力”itfConfiguration”を次のConnectionブロックの入力パラメータ”iftConfiguration”と接続していきましょう。
Connection
次はConnection FBを作成します。
Configuration Blockのパラメータにはすでに必要なパラメータが設置されています。
下図のようにInstnaceが定義されたとき、すでに一部のパラメータが定義されました。

そしてConnection FB Blockの出力”itfConnection”を次のReaderGroupブロックの入力パラメータ”iftConnection”と接続していきましょう。
ReaderGroup
今度はReaderGroupです。
パラメータにもすでに設定されています。
それはReaderGroupのiftReaderGroupDiagnosticsが初期でレアでGroupDiagnosticsに割り当てられたからです。
そしてReaderGroup FB Blockの出力”itfReader”を次のReaderブロックの入力パラメータ”iftReaderGroup”と接続していきましょう。
Reader
Reader Blockの設定も他の通信Blockと一緒です。
パラメータにもすでに設定されています。
それはReaderのiftReaderDiagnosticsが初期でレアでreaderDiagnosticsに割り当てられたからです。
そしてReader FB Blockの出力”itfReader”を次のDataSetにExtendした、先程自作したFB rxSensorDataSet ブロックの入力パラメータ”iftReader”と接続していきましょう。
rxSensorDataSet
rxSensorDataSetはときに設定する項目がなく、ioDataのパラメータは先程GVLで定義した構造体変数と繋がります。
CyclicCall
CyclicCall MethodにEnable信号を入力し、プログラムを周期実行できるようにします。
Method-CyclicCall
こちらはCyclicCallプログラムです。
- Enableが立ち上げる信号を検知したらすべてのBlockにEnable信号をTrueします。
- Enableが立ち下げる信号を検知したらすべてのBlockにEnable信号をFalseします。
ftrig(CLK:=xEnable); rtrig(CLK:=xEnable); IF rtrig.Q THEN // enable the communication edge triggered configuration.Enable(); connection.Enable(); readerGroup.Enable(); reader.Enable(); ELSIF ftrig.Q THEN configuration.Disable(); connection.Disable(); readerGroup.Disable(); reader.Disable(); END_IF |
Download
最後にプロジェクトをDownloadしてください。
WAGO Side
次はWAGO PFC100側を構築します。今回の記事ではPFC100はPublisherの役割です。
GVL
OPC UA Publish用のGVLを作成します。
GVL_SendData
先ほどPOUで定義した構造体を利用し変数を定義します。
{attribute ‘qualified_only’} VAR_GLOBAL udtData:SensorDataStruct; END_VAR |
FB
txSensorDataSet
次はOPC UA Publish用のFBを作成します。
ioDataはFBの外部から変数を渡せるように設置したパラメータです。
{attribute ‘call_after_init’} FUNCTION_BLOCK txSensorDataSet EXTENDS SensorDataSet VAR_INPUT END_VAR VAR_OUTPUT END_VAR VAR END_VAR VAR_IN_OUT ioData :SensorDataStruct; END_VAR SUPER^(); |
Method-PrepareValues
PrepareValuesは各Indexの変数のアドレス番地を転送するプログラムです。
// spread the received data over the application _aIndex[0].pValue:=ADR(ioData.rData1); _aIndex[1].pValue:=ADR(ioData.rData2); _aIndex[2].pValue:=ADR(ioData.location); _aIndex[3].pValue:=ADR(ioData.rData3); _aIndex[4].pValue:=ADR(ioData.rData4); PrepareValues := UADP.ERROR.NO_ERROR; |
Program-Communication_PRG
それでは実際にPubsubの通信プログラムを作成しましょう。
VAR
VAR領域でプログラムに必要な変数を定義します。
PROGRAM Communication_PRG VAR // enable the communication xEnable : BOOL; rootDiagnostic : UADP.RootDiagnostics; configuration : UADP.Configuration := (itfRootDiagnostics:=rootDiagnostic); myAsyncProperty : UADP.AsyncProperty := (tnTaskName:=’C1′, usiTaskPrio := 10, udiTaskInterval:=10000); connectionDiagnostics : UADP.ConnectionDiagnostics; connection : UADP.Connection := (uiPort:=GlobalIP.uiPort, uiPublisherPort:=GlobalIP.uiPort, itfIPAddress:=GlobalIP.ipAddressSensor, itfConnectionDiagnostics:=connectionDiagnostics, itfPublisherIP:=GlobalIP.multicastIpReceiver, itfAsyncProperty:=myAsyncProperty, uiPublisherId:=41); writerGroupDiagnostics : UADP.WriterGroupDiagnostics; writerGroup : UADP.WriterGroup := (itfWriterGroupDiagnostics:=writerGroupDiagnostics, uiWriterGroupId := 22, udiGroupVersion := UADP.DT_TO_UAVersionTime(DT#2020-01-22-13:42:55.123)); writerDiagnostics : UADP.WriterDiagnostics; writer : UADP.Writer := (itfWriterDiagnostics:=writerDiagnostics); writerDataSet : txSensorDataSet; rSinus : REAL; tiNow : LTIME; ftrig : F_TRIG; rtrig : R_TRIG; sineGenerator : Util.GEN := (MODE:=GEN_MODE.SINE, BASE:=TRUE, PERIOD:=T#3S, AMPLITUDE:=100); END_VAR |
FLOW
こちらは通信プログラムの全体的なFLOWになります。
流れとしては設定→接続→Writerなどの設定→通信スタート→周期実行メソットをTRUEになります。
Configuration
最初にConfiguration Blockを設定します。
パラメータ画面上ではすでに診断用の変数と割り当てています。
そしてConfiguration FB Blockの出力”itfConfiguration”を次のConnectionブロックの入力パラメータ”iftConfiguration”と接続していきましょう。
Connection
次はConnection FBを作成します。
Configuration Blockのパラメータにはすでに必要なパラメータが設置されています。
そしてConnection FB Blockの出力”itfConnection”を次のWriterGroupブロックの入力パラメータ”iftConnection”と接続していきましょう
WriteGroup
今度はWriteGroupです。
パラメータにもすでに設定されています
そしてWriterGroup FB Blockの出力”itfReader”を次のWriterrブロックの入力パラメータ”iftWriterGroup”と接続していきましょう。
Writer
Writer Blockの設定も他の通信Blockと一緒です。
パラメータにもすでに設定されています。
そしてWriter FB Blockの出力”itfWriter”を次のDataSetにExtendした、先程自作したFB txSensorDataSet ブロックの入力パラメータ”iftReader”と接続していきましょう。
tsSensorDatSet
txSensorDataSetはときに設定する項目がなく、ioDataのパラメータは先程GVLで定義した構造体変数と繋がります。
CyclicCall
CyclicCall MethodにEnable信号を入力し、プログラムを周期実行できるようにします。
Method-CyclicCall
こちらはCyclicCallプログラムです。
- Enableが立ち上げる信号を検知したらすべてのBlockにEnable信号をTrueします。
- Enableが立ち下げる信号を検知したらすべてのBlockにEnable信号をFalseします。
そしてPublishするデータを可変するようにプログラムします。
METHOD CyclicCall : BOOL VAR_INPUT xEnable : BOOL; END_VAR VAR iSinOut : INT; END_VAR ftrig(CLK:=xEnable); rtrig(CLK:=xEnable); IF rtrig.Q THEN // enable the communication edge triggered configuration.Enable(); connection.Enable(); writerGroup.Enable(); writer.Enable(); ELSIF ftrig.Q THEN configuration.Disable(); connection.Disable(); writerGroup.Disable(); writer.Disable(); END_IF sineGenerator(OUT=>iSinOut); rSinus := TO_REAL(iSinOut) / 100.0; {IF defined(pou: Util.GetDateTime)} tiNow := TO_LTIME(Util.GetDateTime()*1000); {ELSE} tiNow := TO_LTIME(TO_DWORD(DATE_AND_TIME#2020-08-01-12:00:00)*1E6); {END_IF} |
Download
最後にプロジェクトをDownloadしてください。
Result
Done!下図のようなBerghof MC-PIコントローラーとWAGOのPFC100コントローラーはOPC UA PubSubで通信できるようになりました!