OPC クライアントのアプリケーション(OPC DA Classic)を C#で作成した時のメモです。
OPC Server に [接続]して [タグ値の読込] [タグへの値書込]と[クローズ] をおこないました。
出来る限り,C#コードの説明を中心に,関連事項は簡素化してます。
##準備したもの
OPC Server と PLC
PLCをOPC ServerにEthernetで接続しタグの現在値を表示できるようにします。
・OPC Server: たけびしDeviceXPlore v6.4.0.1(x86)デモ版をインストール。(1時間動きます)
・PLC: 三菱電機 FX5UCPU。
・GX Works3 でPLCの内蔵Ethernetポートを設定します。
(GX Works3 Version1 1.066U を使いました)
・OPC Serverを操作して PLCとの接続, 数点のタグを登録します。
(OPC Server と GX Works3 の操作は,説明書を参照しました)
・OPC Serverをインストールすると同時にインストールされるOPC Clientを使ってタグの値の
読込/書込の操作を行っておくとアプリケーション作成の理解に手助けとなります。
C#
・Visual Studio 2017 で 新規プロジェクト
[Windows フォームアプリケーション(.NET Framework) Visual C#] を作成しました。
・[プロジェクト] -> [参照の追加] を選択して表示される [参照マネージャー] から
[タイプライブラリ]を選択し[OPC DA Automation Wrapper] にチェックを入れて[OK]します。
(VS2019では [OPC DA Automation Wrapper]は表示されませんでした。)
・Form1のコード Form1.cs を開きます。 using OPCAutomation を追加します。
オブジェクトブラウザーでOPCAutomationが確認できます。
using System;
using System.xxxxxxx;
//
//
using OPCAutomation;
・OPC ServerをインストールしたPCとC#で作成のOPC Client アプリケーションは同じPCで
起動してください。別のPCで起動する場合,接続のための設定が必要となります。
##参考コード##
(ひとつの例に過ぎません。間違いがあったらお知らせ頂くと有り難いです)
OPCServer object を 宣言
// OPC Server object
public OPCServer ConnectedOPCServer;
// OPCGroups object
public OPCGroup ConnectedGroup;
引数にOPC Server の情報を入れて,
sProgID="Takebishi.Dxp.6"; OPC ServerのID
sNode="XXX.XXX.XXX.XXX"; ipアドレス
さて, OPC Server へ Connect で [接続]...。
public bool StartOpc(string sProgID, string sNode) {
sErrMsg = "";
try {
ConnectedOPCServer = new OPCServer();
ConnectedOPCServer.Connect(sProgID, sNode);
}
catch (Exception ex) {
sErrMsg = ex.Message;
return true;
}
return false;
}
グループ名のAdd
次にグループ名をAddします。そして,OPC Serverに既に登録されているタグ名をAdd します。
・引数 sIOGroup="Group1"; として OPCGroups.Add(sIOGroup) しました。
グループ名は,識別できる文字列を設定します。
public bool AddGroupOPC(string sIOGroup) {
sErrMsg = "";
try {
ConnectedGroup = ConnectedOPCServer.OPCGroups.Add(sIOGroup);
ConnectedGroup.UpdateRate = 1000;
}
catch (Exception ex) {
sErrMsg = ex.Message;
return true;
}
return false;
}
TAG名称のAdd
・OPCItems.AddItems には配列(OPCItemIDs)でTAG名を渡します。
この他にもパラメータとして渡す配列が必要です。(全部で6コ)
また,これらの配列は基数が[1]から処理されます。配列に値を設定する場合,
その添字には注意が必要です。
・OPC Serverに,タグを3つ登録し名称を, [1運転], [2停止], [3出力]としました。
それぞれ PLCレジスタは M0, M1, M100 に設定しています。
一方,PLC側に M0, M1, M100で自己保持回路をセットし,M100 の接点を Y0 に接続しています。
・配列の宣言,割付です。タグの数はiNumOfItem=4として設定します。
public int iNumOfItem { get; set; }
public Array OPCItemIDs;
public Array ClientHandles;
public Array ItemServerHandles;
public Array ItemServerErrors;
public Array RequestedDataTypes;
public Array AccessPaths;
// ・
// ・
OPCItemIDs = Array.CreateInstance(typeof(string), iNumOfItem);
ClientHandles = Array.CreateInstance(typeof(Int32), iNumOfItem);
ItemServerHandles = Array.CreateInstance(typeof(Int32), iNumOfItem);
ItemServerErrors = Array.CreateInstance(typeof(Int32), iNumOfItem);
RequestedDataTypes = Array.CreateInstance(typeof(Int16), iNumOfItem);
AccessPaths = Array.CreateInstance(typeof(string), iNumOfItem);
// ・
// ・
・OPCItemIDsにタグ名称を設定します。配列[1]からの扱いとなります。タグ名称には
[Device1.]を付加しておきます。([Device1.]は,OPC Serverに依る文字列ですが設定も可能です)
OPCItemIDs.SetValue("Device1.1運転", 1);
OPCItemIDs.SetValue("Device1.2停止", 2);
OPCItemIDs.SetValue("Device1.3出力", 3);
・それでは,OPCItems.AddItems にパラメータを渡します。
public bool AddItemOPC() {
sErrMsg = "";
try {
ConnectedGroup.OPCItems.DefaultIsActive = true;
ConnectedGroup.OPCItems.AddItems(iNumOfItem - 1, ref OPCItemIDs, ref ClientHandles, out ItemServerHandles, out ItemServerErrors, RequestedDataTypes, AccessPaths);
}
catch (Exception ex) {
sErrMsg = ex.Message;
return true;
}
return false;
}
タグへの値書込と読込
・これには同期と非同期の2つの方式があります。今回は,同期の方式のみを扱います。
非同期はデリゲートを設定して動作完了時に呼び出しされることになります。
次回の機会に扱うことにします。
・配列に値を代入して引数としますので宣言, 割付けします。
public Array aryValues;
public Array aryErrors;
// ・
// ・
aryValues = Array.CreateInstance(typeof(object), iNumOfItem);
aryErrors = null;
// ・
// ・
同期書込
・引数:タグのID(sIdItem)="1",書込値(sValWrt)="1"で書込むとタグ名称[1運転]の値が[ON]
となります。PLCのラダーが処理して,[3出力]:ON, PLCの[Y0]:ON となります。
すると同時に,PLC本体前面の[Y0]位置LEDが点灯するはずです。同様に,
引数:タグのID(sIdItem)="2",書込値(sValWrt)="1"で書込むとタグ名称[2停止]の値が[ON]
となります。PLCのラダーが処理して[3出力]:OFF,PLCの[Y0]:OFFとなります。
今度は,PLC本体前面の[Y0]位置LEDが消灯するはずです。
・実際に,sIdItem="1", sValWrt="1" としてSyncWrt_OPCに引数を設定したところ,
PLCの[Y0]位置のLEDが点灯しました。
public void SyncWrt_OPC(string sIdItem, string sValWrt) {
sErrMsg = "";
int iIdItem = Convert.ToInt32(sIdItem);
Array.Clear(aryValues, 0, aryValues.Length);
aryValues.SetValue(sValWrt, iIdItem);
try {
ConnectedGroup.SyncWrite(iIdItem, ref ItemServerHandles, ref aryValues, out aryErrors);
}
catch (Exception ex) {
sErrMsg = ex.Message;
}
}
・次に,sIdItem="2", sValWrt="1" で試ました。PLCの[Y0]位置のLEDが消灯しました。
同期読込
・引数:タグのID(sIdItem)="1" とすれば RdValueにはタグの値, shQualitiesに品質,
dtTimeStampにタイムスタンプが入ってきます。
public bool SyncRead_OPC(string sIdItem, ref string RdValue, ref short shQualities, ref DateTime dtTimeStamp) {
sErrMsg = "";
int iIdItem = Convert.ToInt32(sIdItem);
System.Array aryRdValues;
System.Array aryErrors;
object obQualities = new object();
object obTimeStamp = new object();
int iNumItems = 1;
try {
Array SyncServerHandles = Array.CreateInstance(typeof(Int32), iNumOfItem);
SyncServerHandles.SetValue(ItemServerHandles.GetValue(iIdItem), 1); // SyncServerHandles[1]に書込む
ConnectedGroup.SyncRead((short)OPCDataSource.OPCDevice, iNumItems, ref SyncServerHandles, out aryRdValues, out aryErrors, out obQualities, out obTimeStamp);
var RdError = aryErrors.GetValue(1);
bool bErr = Object.Equals(RdError, 0);
if (!bErr) {
return true;
}
var vdRdValue = aryRdValues.GetValue(1);
RdValue = Convert.ToString (vdRdValue);
var aryQualities = (Array)obQualities;
shQualities = (short)aryQualities.GetValue(1);
var aryTimeStamp = (Array)obTimeStamp;
dtTimeStamp = (DateTime)aryTimeStamp.GetValue(1);
}
catch (Exception ex) {
sErrMsg = ex.Message;
return true;
}
return false;
}
クローズ処理
・引数 sGrpName="Group1"として OPCItemsをRemove, OPCGroupsをRemovesして Disconnect()します。
public bool FinishOpc(string sGrpName) {
sErrMsg = "";
try {
if (ConnectedGroup != null) {
ConnectedGroup.OPCItems.Remove(iNumOfItem - 1, ref ItemServerHandles, out aryErrors);
ConnectedOPCServer.OPCGroups.Remove(sGrpName);
ConnectedGroup.IsSubscribed = false;
}
ConnectedOPCServer.Disconnect();
}
catch (Exception ex) {
sErrMsg = ex.Message;
return false;
}
return true;
}
終わり
・OPC Classic で 単純な[接続],[読込],[書込],[クローズ]を行ってみました。
OPC Server のタグの登録するにあたって(余談)
・OPC Serverにタグを登録する際,PLCの説明書でPLCデバイスの種類と目的を確認
してください。
OPC Serverのタグ登録ではPLCデバイス種類を X,Y,M,B... と簡単に選択できますが,PLC側では
デバイスの種類は用途によって大きく異なります。例えば, PLC:FX5UCPUのデバイス種類
の[Y]の目的は出力です。OPCタグを[Y]に割付けるとOPCタグ値の1/0 がPLCの出力端子に
ON/OFFとしてそのまま出力されます。
(最終の出力はIOリフレッシュがあるのでPLC側プログラムに依ります)
PLCが設置される"XX盤"ではOPCのタグといえども外部信号には違いありません。外部信号は
端子台からPLCへ結線されるものです。ところが結線は不可能なので,OPCタグの登録には
慎重さが必要となります。OPCタグをいきなり[Y]に割付したりして...
たとえが古いですが,操作盤の[遠方]<->[手元]のセレクタを[遠方]にした途端にマグネットが
どうなるか... 推して知るべし です。
##参照した資料
https://www.codeproject.com/Articles/490072/DA-1-OPC-Wrapper-DLL-and-Client-Example
Data Access Automation Interface Standard Version 2.02 February 4, 1999