Codesys#OPCUA PubSubを使ってみよう

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

さ、FAを楽しもう。

Reference Link

PLCNEXT#OPC UA PubSub使ってみよう

前書き

いつも私の技術ブログと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

https://x.com/3threes2

技術はひとりじゃもったいない。

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で通信できるようになりました!

シェアする

  • このエントリーをはてなブックマークに追加

フォローする