2
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

今さら OPC DA で接続してみる

Last updated at Posted at 2021-10-12

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

2
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?