下面将会简单介绍下OPC DA客户端数据访问,以及搭配整个系统的运行环境。

  • OPC(OLE for Process Control)其实就是一套标准,我对这套标准理解不多,使用过程中就把它理解一套协议或者规范,主要用于工控领域。OPC中有很多规范,我主要使用OPC DA规范来进行数据的读写操作。还有其他规范,比如OPC UA、OPC HDA等。如果你做的是OPC Server开发查下这方面的资料了解下,这篇博文主要介绍OPC Client开发的知识。

使用OPC DA进行Client的读写操作时,我们使用Custom接口,出此之外还有Automation接口。以下是Custome接口开发时涉及到的三个关键对象:OpcServer、OpcGroup、OpcItem,下图是他们之间的逻辑关系:

在客户端开发时,要使用OpcServer对象来实现客户端与Opc服务器之间的连接。一个OpcServer对象下有多个OpcGroup,一个OpcGroup下有多个OpcItem,在自定义接口下的Client开发,是以Group为单位的操作,数据读写都是通过OpcGroup进行的。

  • 搭建程序运行环境

程序运行需要的软硬件环境:

    1. .Net Framework 4.0
    2. Simatic Net 2008(Or Other) HF1
    3. 西门子300(Or Other) PLC

我们可以通过本机的配置来实现OPC的远程连接,我没有采用这种方式,一是这种配置比较麻烦,而是这种方式不稳定。所以我采用本机安装一个OPCServer来实现与PLC的交互。

对于OPCServer软件,我选择的是SimaticNet 2008 HF1(安装WinCC的时候会有选择安装SimaticNet的选项),没有特别的原因,就是比较熟悉了而已,而且PLC选用的是西门子的。

我们可以不写OPC Client程序来测试,如何通过OPCServer与PLC之间的交互。首先当我们安装完毕SimaticNet之后,需要对Station Configuration Editor进行配置,如下图:

首先我们要指定Station的名称,上图叫PCStation,点击下方的StationName可以进行更改。下一步在1号栈上选择一个OPCServer,3号栈上选择一个通信网卡。

接下来我们需要在Step 7中建立Station Configuration Editor与PLC之间的连接,我们暂且叫组态。组态的过程中要建立与Station Configuration Editor中对应的Opc Server和IE General(所在栈号相同),Station Configuration Edition起到桥接的作用 用,主要让PLC与Opc Server之间建立一条S7连接。暂时没有拿到组态图,以后补上。

当我们组态完毕时,如何判断组态是否正确呢?在SimaticNet的目录上有个叫Opc Scout(Opc Scout V10)的软件,打开如下图:

上图列出来了本机所有的Server,我们能使用名为OPC.SimaticNET的Server。双击这个Server添加一个组,多次双击这个Server可以添加多个组,验证了上图的Server与Group的关系了。

我们双击新建的Group,进入如下图的界面:

上图列出了所有的连接。上文说到的组态中建立的S7连接可以在S7节点中看到,展开这个节点可以看到我们建立的S7连接,如下图:

上图列出了名为S7 connection_1的S7连接,展开Object对象,列出PLC的结构。我们选择一种来新建我们的Item,由于我这里没有PLC模块,所以无法截图给大家看。

至此我们的OPC Client的运行环境搭建完毕。

  • 编写OPC Client端程序。

我们需要使用OPC Foundation提供的自定义接口来进行开发,在Visual Studio引用名为:OpcRcw.Comn.dll和OpcRcw.Da.dll这两个DLL。

我们定义一个名为OpcDaCustomAsync的类,让这个类继承自:IOPCDataCallback,IDisposable

  1 using System;
  2 using System.Collections.Generic;
  3 using OpcRcw.Comn;
  4 using OpcRcw.Da;
  5 using System.Runtime.InteropServices;
  6  7 namespace Opc.Net
  8 {
  9     /// <summary> 10     /// Opc自定义接口-异步管理类 11     /// <author name="lm" date="2012.3.14"/> 12     /// </summary> 13     public class OpcDaCustomAsync : IOPCDataCallback,IDisposable
 14     {
 15         /// <summary> 16         /// OPC服务器对象 17         /// </summary> 18         IOPCServer iOpcServer;
 19         /// <summary> 20         /// 事务ID 21         /// </summary> 22         int transactionID;
 23         /// <summary> 24         /// OPC服务器名称 25         /// </summary> 26         string opcServerName;
 27         /// <summary> 28         /// OPC服务器IP地址 29         /// </summary> 30         IOPCAsyncIO2 _iopcAsyncIo2;
 31         /// <summary> 32         /// OPC服务器IP地址 33         /// </summary> 34         string opcServerIPAddress;
 35         /// <summary> 36         /// Opc组列表 37         /// </summary> 38         List<OpcDaCustomGroup> opcDaCustomGroups;
 39         /// <summary> 40         /// 连接指针容器 41         /// </summary> 42         IConnectionPointContainer IConnectionPointContainer = null;
 43         /// <summary> 44         /// 连接指针 45         /// </summary> 46         IConnectionPoint IConnectionPoint = null;
 47         /// <summary> 48         /// Opc组管理器 49         /// </summary> 50         IOPCGroupStateMgt IOPCGroupStateMgt = null;
 51 52 53         //接收数据事件 54         public event EventHandler<OpcDaCustomAsyncEventArgs> OnDataChanged;
 55         /// <summary> 56         /// 异步写入数据完成事件 57         /// </summary> 58         public event EventHandler<OpcDaCustomAsyncEventArgs> OnWriteCompleted;
 59         /// <summary> 60         /// 异步读取数据完成事件 61         /// </summary> 62         public event EventHandler<OpcDaCustomAsyncEventArgs> OnReadCompleted;
 63 64         /// <summary> 65         /// 构造函数 66         /// </summary> 67         /// <param name="opcDaCustomGroups">Opc组列表</param> 68         /// <param name="opcServerName">OPC服务器名称</param> 69         /// <param name="opcServerIpAddress">OPC服务器IP地址</param> 70         public OpcDaCustomAsync(List<OpcDaCustomGroup> opcDaCustomGroups, string opcServerName, string opcServerIpAddress)
 71         {
 72             this.opcDaCustomGroups = opcDaCustomGroups;
 73             this.opcServerName = opcServerName;
 74             this.opcServerIPAddress = opcServerIpAddress;
 75             Init();
 76         }
 77         /// <summary> 78         /// 初始化参数 79         /// </summary> 80         public void Init()
 81         {
 82             if (Connect())
 83             {
 84                 AddOpcGroup();
 85             }
 86         }
 87 88         /// <summary> 89         /// 连接Opc服务器 90         /// </summary> 91         /// <returns></returns> 92         public bool Connect()
 93         {
 94             return Connect(opcServerName, opcServerIPAddress);
 95         }
 96         /// <summary> 97         /// 连接Opc服务器 98         /// </summary> 99         /// <returns></returns>100         public bool Connect(string remoteOpcServerName, string remoteOpcServerIpAddress)
101         {
102             var returnValue = false;
103             if (!string.IsNullOrEmpty(remoteOpcServerIpAddress) && !string.IsNullOrEmpty(remoteOpcServerName))
104             {
105                 var opcServerType = Type.GetTypeFromProgID(remoteOpcServerName, remoteOpcServerIpAddress);
106                 if (opcServerType != null)
107                 {
108                     iOpcServer = (IOPCServer)Activator.CreateInstance(opcServerType);
109                     returnValue = true;
110                 }
111             }  
112             return returnValue;
113         }
114         /// <summary>115         /// 添加Opc组116         /// </summary>117         private void AddOpcGroup()
118         {
119             try120             {
121                 foreach (OpcDaCustomGroup opcGroup in opcDaCustomGroups)
122                 {
123                     AddOpcGroup(opcGroup);
124                 }
125             }
126             catch(COMException ex)
127             {
128                 throw ex;
129             }
130         }
131         /// <summary>132         /// 添加Opc项133         /// </summary>134         /// <param name="opcGroup"></param>135         private void AddOpcGroup(OpcDaCustomGroup opcGroup)
136         {
137             try138             {
139140                 //添加OPC组141                 iOpcServer.AddGroup(opcGroup.GroupName, opcGroup.IsActive, opcGroup.RequestedUpdateRate, opcGroup.ClientGroupHandle, opcGroup.TimeBias.AddrOfPinnedObject(), opcGroup.PercendDeadBand.AddrOfPinnedObject(), opcGroup.LCID, out opcGroup.ServerGroupHandle, out opcGroup.RevisedUpdateRate, ref opcGroup.Riid, out opcGroup.Group);
142                 InitIoInterfaces(opcGroup);
143                 if (opcGroup.OpcDataCustomItems.Length > 0)
144                 {
145                     //添加OPC项146                     AddOpcItem(opcGroup);
147                     //激活订阅回调事件148                     ActiveDataChanged(IOPCGroupStateMgt);
149                 }
150             }
151             catch (COMException ex)
152             {
153                 throw ex;
154             }
155             finally156             {
157                 if (opcGroup.TimeBias.IsAllocated)
158                 {
159                     opcGroup.TimeBias.Free();
160                 }
161                 if (opcGroup.PercendDeadBand.IsAllocated)
162                 {
163                     opcGroup.PercendDeadBand.Free();
164                 }
165             }
166         }
167         /// <summary>168         /// 初始化IO接口169         /// </summary>170         /// <param name="opcGroup"></param>171         public void InitIoInterfaces(OpcDaCustomGroup opcGroup)
172         {
173             int cookie;
174             //组状态管理对象,改变组的刷新率和激活状态175             IOPCGroupStateMgt = (IOPCGroupStateMgt)opcGroup.Group;
176             IConnectionPointContainer = (IConnectionPointContainer)opcGroup.Group;
177             Guid iid = typeof(IOPCDataCallback).GUID;
178             IConnectionPointContainer.FindConnectionPoint(ref iid, out IConnectionPoint);
179             //创建客户端与服务端之间的连接180             IConnectionPoint.Advise(this, out181                     cookie);
182         }
183         /// <summary>184         /// 激活订阅回调事件185         /// </summary>186         private void ActiveDataChanged(IOPCGroupStateMgt IOPCGroupStateMgt)
187         {
188             IntPtr pRequestedUpdateRate = IntPtr.Zero;
189             IntPtr hClientGroup = IntPtr.Zero;
190             IntPtr pTimeBias = IntPtr.Zero;
191             IntPtr pDeadband = IntPtr.Zero;
192             IntPtr pLCID = IntPtr.Zero;
193             int nActive = 0;
194             GCHandle hActive = GCHandle.Alloc(nActive, GCHandleType.Pinned);
195             try196             {
197                 hActive.Target = 1;
198                 int nRevUpdateRate = 0;
199                 IOPCGroupStateMgt.SetState(pRequestedUpdateRate, out nRevUpdateRate,
200                         hActive.AddrOfPinnedObject(), pTimeBias, pDeadband, pLCID, hClientGroup);
201             }
202             catch (COMException ex)
203             {
204                 throw ex;
205             }
206             finally207             {
208                 hActive.Free();
209             }
210         }
211212         /// <summary>213         /// 添加Opc项214         /// </summary>215         /// <param name="opcGroup"></param>216         private void AddOpcItem(OpcDaCustomGroup opcGroup)
217         {
218             OpcDaCustomItem[] opcDataCustomItemsService = opcGroup.OpcDataCustomItems;
219             IntPtr pResults = IntPtr.Zero;
220             IntPtr pErrors = IntPtr.Zero;
221             OPCITEMDEF[] itemDefyArray = new OPCITEMDEF[opcGroup.OpcDataCustomItems.Length];
222             int i = 0;
223             int[] errors = new int[opcGroup.OpcDataCustomItems.Length];
224             int[] itemServerHandle = new int[opcGroup.OpcDataCustomItems.Length];
225             try226             {
227                 foreach (OpcDaCustomItem itemService in opcDataCustomItemsService)
228                 {
229                     if (itemService != null)
230                     {
231                         itemDefyArray[i].szAccessPath = itemService.AccessPath;
232                         itemDefyArray[i].szItemID = itemService.ItemID;
233                         itemDefyArray[i].bActive = itemService.IsActive;
234                         itemDefyArray[i].hClient = itemService.ClientHandle;
235                         itemDefyArray[i].dwBlobSize = itemService.BlobSize;
236                         itemDefyArray[i].pBlob = itemService.Blob;
237                         itemDefyArray[i].vtRequestedDataType = itemService.RequestedDataType;
238                         i++;
239                     }
240241                 }
242                 //添加OPC项组243                 ((IOPCItemMgt)opcGroup.Group).AddItems(opcGroup.OpcDataCustomItems.Length, itemDefyArray, out pResults, out pErrors);
244                 IntPtr Pos = pResults;
245                 Marshal.Copy(pErrors, errors, 0, opcGroup.OpcDataCustomItems.Length);
246                 for (int j = 0; j < opcGroup.OpcDataCustomItems.Length; j++)
247                 {
248                     if (errors[j] == 0)
249                     {
250                         if (j != 0)
251                         {
252                             Pos = new IntPtr(Pos.ToInt32() + Marshal.SizeOf(typeof(OPCITEMRESULT)));
253                         }
254                         var result = (OPCITEMRESULT)Marshal.PtrToStructure(Pos, typeof(OPCITEMRESULT));
255                         itemServerHandle[j] = opcDataCustomItemsService[j].ServerHandle = result.hServer;
256                         Marshal.DestroyStructure(Pos, typeof(OPCITEMRESULT));
257                     }
258                 }
259             }
260             catch (COMException ex)
261             {
262                 throw ex;
263             }
264             finally265             {
266                 if (pResults != IntPtr.Zero)
267                 {
268                     Marshal.FreeCoTaskMem(pResults);
269                 }
270                 if (pErrors != IntPtr.Zero)
271                 {
272                     Marshal.FreeCoTaskMem(pErrors);
273                 }
274             }
275         }
276         /// <summary>277         /// 异步读取信息278         /// </summary>279         public void Read()
280         {
281             foreach (OpcDaCustomGroup opcGroup in opcDaCustomGroups)
282             {
283                 IntPtr pErrors = IntPtr.Zero;
284                 try285                 {
286                     _iopcAsyncIo2 = (IOPCAsyncIO2)opcGroup.Group;
287                     if (_iopcAsyncIo2 != null)
288                     {
289                         int[] serverHandle = new int[opcGroup.OpcDataCustomItems.Length];
290                         opcGroup.PErrors = new int[opcGroup.OpcDataCustomItems.Length];
291                         for (int j = 0; j < opcGroup.OpcDataCustomItems.Length; j++)
292                         {
293                             serverHandle[j] = opcGroup.OpcDataCustomItems[j].ServerHandle;
294                         }
295                         int cancelId=0;
296                         _iopcAsyncIo2.Read(opcGroup.OpcDataCustomItems.Length, serverHandle, 2, out cancelId, out pErrors);
297                         Marshal.Copy(pErrors, opcGroup.PErrors, 0, opcGroup.OpcDataCustomItems.Length);
298                     }
299                 }
300                 catch (COMException ex)
301                 {
302                     throw ex;
303                 }
304                 finally305                 {
306                     if (pErrors != IntPtr.Zero)
307                     {
308                         Marshal.FreeCoTaskMem(pErrors);
309                     }
310                 }
311             }
312         }
313314         /// <summary>315         /// 异步写入数据316         /// </summary>317         /// <param name="values">要写入的值</param>318         /// <param name="serverHandle">要写入的项的服务器句柄</param>319         /// <param name="errors">错误信息,等于表示写入成功,否则写入失败</param>320         /// <param name="opcGroup">要写入的Opc组</param>321         public void Write(object[] values,int[] serverHandle,out int[] errors,OpcDaCustomGroup opcGroup)
322         {
323             _iopcAsyncIo2 = (IOPCAsyncIO2)opcGroup.Group;
324             IntPtr pErrors = IntPtr.Zero;
325             errors = new int[values.Length];
326             if (_iopcAsyncIo2 != null)
327             {
328                 try329                 {
330                     //异步写入数据331                     int cancelId;
332                     _iopcAsyncIo2.Write(values.Length, serverHandle, values, transactionID + 1, out cancelId, out pErrors);
333                     Marshal.Copy(pErrors, errors, 0, values.Length);
334                 }
335                 catch (COMException ex)
336                 {
337                     throw ex;
338                 }
339                 finally340                 {
341                     if (pErrors != IntPtr.Zero)
342                     {
343                         Marshal.FreeCoTaskMem(pErrors);
344                     }
345                 }
346             }
347         }
348         /// <summary>349         /// 数据订阅事件350         /// </summary>351         /// <param name="dwTransid"></param>352         /// <param name="hGroup"></param>353         /// <param name="hrMasterquality"></param>354         /// <param name="hrMastererror"></param>355         /// <param name="dwCount"></param>356         /// <param name="phClientItems"></param>357         /// <param name="pvValues"></param>358         /// <param name="pwQualities"></param>359         /// <param name="pftTimeStamps"></param>360         /// <param name="pErrors"></param>361         public virtual void OnDataChange(Int32 dwTransid,
362             Int32 hGroup,
363             Int32 hrMasterquality,
364             Int32 hrMastererror,
365             Int32 dwCount,
366             int[] phClientItems,
367             object[] pvValues,
368             short[] pwQualities,
369             OpcRcw.Da.FILETIME[] pftTimeStamps,
370             int[] pErrors)
371372         {
373             var e = new OpcDaCustomAsyncEventArgs
374             {
375                 GroupHandle = hGroup,
376                 Count = dwCount,
377                 Errors = pErrors,
378                 Values = pvValues,
379                 ClientItemsHandle = phClientItems
380             };
381             if (OnDataChanged != null)
382             {
383                 OnDataChanged(this, e);
384             }
385         }
386387         /// <summary>388         /// 取消事件389         /// </summary>390         /// <param name="dwTransid"></param>391         /// <param name="hGroup"></param>392         public virtual void OnCancelComplete(Int32 dwTransid, Int32 hGroup)
393         {
394395         }
396397         /// <summary>398         /// 写入数据完成事件399         /// </summary>400         /// <param name="dwTransid"></param>401         /// <param name="hGroup"></param>402         /// <param name="hrMastererr"></param>403         /// <param name="dwCount"></param>404         /// <param name="pClienthandles"></param>405         /// <param name="pErrors"></param>406         public virtual void OnWriteComplete(Int32 dwTransid,
407             Int32 hGroup,
408             Int32 hrMastererr,
409             Int32 dwCount,
410             int[] pClienthandles,
411             int[] pErrors)
412         {
413             if (OnWriteCompleted != null)
414             {
415                 var e = new OpcDaCustomAsyncEventArgs
416                 {
417                     Errors = pErrors
418                 };
419                 if (OnWriteCompleted != null)
420                 {
421                     OnWriteCompleted(this, e);
422                 }
423             }
424         }
425         /// <summary>426         /// 读取数据完成事件427         /// </summary>428         /// <param name="dwTransid"></param>429         /// <param name="hGroup"></param>430         /// <param name="hrMasterquality"></param>431         /// <param name="hrMastererror"></param>432         /// <param name="dwCount">要读取的组的项的个数</param>433         /// <param name="phClientItems"></param>434         /// <param name="pvValues">项值列表</param>435         /// <param name="pwQualities"></param>436         /// <param name="pftTimeStamps"></param>437         /// <param name="pErrors">项错误列表</param>438         public virtual void OnReadComplete(Int32 dwTransid,
439             Int32 hGroup,
440             Int32 hrMasterquality,
441             Int32 hrMastererror,
442             Int32 dwCount,
443             int[] phClientItems,
444             object[] pvValues,
445             short[] pwQualities,
446             OpcRcw.Da.FILETIME[] pftTimeStamps,
447             int[] pErrors)
448         {
449             if (OnReadCompleted != null)
450             {
451                 var e = new OpcDaCustomAsyncEventArgs
452                 {
453                     GroupHandle = hGroup,
454                     Count = dwCount,
455                     Errors = pErrors,
456                     Values = pvValues,
457                     ClientItemsHandle = phClientItems
458                 };
459                 OnReadCompleted(this, e);
460             }
461         }
462         public void Dispose()
463         {
464465         }
466     }
467 }

我们看下IOPCDataCallback接口的定义:

这个接口提供了4个函数。如果我们采用订阅模式(默认的模式),会执行OnDataChange函数,主动读数据则执行OnReadComplete函数,写数据则执行OnWriteComplete函数。在OpcDaCustomAsync类中,我已经对这四个函数进行了实现,每个实现对应一个事件。

OpcDaCustomAsync类的实现,我主要是扒了SimaticNet下的一个Sample,自己封装了下。使用这个类的时候需要提供Group列表和OPCServer的名称,以及OPCServer所在的主机的IP地址。

OpcGroup的封装:

  1 using System;
  2 using System.Runtime.InteropServices;
  3 using OpcRcw.Da;
  4  5 namespace Opc.Net
  6 {
  7     /// <summary>  8     /// 自定义接口OPC组对象  9     /// </summary> 10     public class OpcDaCustomGroup
 11     {
 12         private string groupName;
 13         private int isActive=1;
 14         private int requestedUpdateRate;
 15         private int clientGroupHandle=1;
 16         private GCHandle timeBias = GCHandle.Alloc(0, GCHandleType.Pinned);
 17         private GCHandle percendDeadBand = GCHandle.Alloc(0, GCHandleType.Pinned);
 18         private int lcid = 0x409;
 19         private int itemCount;
 20         private bool onRead;
 21 22         /// <summary> 23         /// 输出参数,服务器为新创建的组对象产生的句柄 24         /// </summary> 25         public int ServerGroupHandle;
 26 27         /// <summary> 28         /// 输出参数,服务器返回给客户端的实际使用的数据更新率 29         /// </summary> 30         public int RevisedUpdateRate;
 31 32         /// <summary> 33         /// 引用参数,客户端想要的组对象的接口类型(如 IIDIOPCItemMgt) 34         /// </summary> 35         public Guid Riid = typeof(IOPCItemMgt).GUID;
 36 37         /// <summary> 38         /// 输出参数,用来存储返回的接口指针。如果函数操作出现任务失败,此参数将返回NULL。 39         /// </summary> 40         public object Group;
 41         private OpcDaCustomItem[] opcDataCustomItems;
 42 43         public int[] PErrors { get; set; }
 44 45         /// <summary> 46         /// 组对象是否激活 47         /// 1为激活,0为未激活,默认激活 48         /// </summary> 49         public int IsActive
 50         {
 51             get 52             {
 53                 return isActive;
 54             }
 55             set 56             {
 57                 if (isActive == value)
 58                     return;
 59                 isActive = value;
 60             }
 61         }
 62         /// <summary> 63         /// 组是否采用异步读方式 64         /// </summary> 65         public bool OnRead
 66         {
 67             get 68             {
 69                 return onRead;
 70             }
 71             set 72             {
 73                 if (onRead == value)
 74                     return;
 75                 onRead = value;
 76             }
 77         }
 78         /// <summary> 79         /// 项的个数 80         /// </summary> 81         public int ItemCount
 82         {
 83             get { return itemCount; }
 84             set 85             {
 86                 if(itemCount == value)
 87                     return;
 88                 itemCount=value;
 89             }
 90         }
 91         /// <summary> 92         /// 客户端指定的数据变化率 93         /// </summary> 94         public int RequestedUpdateRate
 95         {
 96             get 97             {
 98                 return requestedUpdateRate;
 99             }
100             set101             {
102                 if (requestedUpdateRate == value)
103                     return;
104                 requestedUpdateRate = value;
105             }
106         }
107108         /// <summary>109         /// OPC组名称110         /// </summary>111         public string GroupName
112         {
113             get114             {
115                 return groupName;
116             }
117             set118             {
119                 if (groupName == value)
120                     return;
121                 groupName = value;
122             }
123         }
124125         /// <summary>126         /// 客户端程序为组对象提供的句柄127         /// </summary>128         public int ClientGroupHandle
129         {
130             get131             {
132                 return clientGroupHandle;
133             }
134             set135             {
136                 if (clientGroupHandle == value)
137                     return;
138                 clientGroupHandle = value;
139             }
140         }
141142         /// <summary>143         /// 指向Long类型的指针144         /// </summary>145         public GCHandle TimeBias
146         {
147             get148             {
149                 return timeBias;
150             }
151             set152             {
153                 if (timeBias == value)
154                     return;
155                 timeBias = value;
156             }
157         }
158159         /// <summary>160         /// 一个项对象的值变化的百分比,可能引发客户端程序的订阅回调。161         /// 此参数只应用于组对象中有模拟dwEUType(工程单位)类型的项对象。指针为NULL表示0.0162         /// </summary>163         public GCHandle PercendDeadBand
164         {
165             get166             {
167                 return percendDeadBand;
168             }
169             set170             {
171                 if (percendDeadBand == value)
172                     return;
173                 percendDeadBand = value;
174             }
175         }
176177         /// <summary>178         /// 当用于组对象上的操作的返回值为文本类型时,服务器使用的语言179         /// </summary>180         public int LCID
181         {
182             get183             {
184                 return lcid;
185             }
186             set187             {
188                 if (lcid == value)
189                     return;
190                 lcid = value;
191             }
192         }
193194         /// <summary>195         /// OPC项数组196         /// </summary>197         public OpcDaCustomItem[] OpcDataCustomItems
198         {
199             get200             {
201                 return opcDataCustomItems;
202             }
203             set204             {
205                 if (opcDataCustomItems != null && opcDataCustomItems == value)
206                     return;
207                 opcDataCustomItems = value;
208             }
209         }
210     }
211 }

OpcItem的封装:

  1 using System;
  2 using System.Collections.Generic;
  3 using System.Linq;
  4 using System.Text;
  5 using System.Runtime.InteropServices;
  6 using OpcRcw.Da;
  7  8 namespace Opc.Net
  9 {
 10     /// <summary> 11     /// 自定义接口Opc项 12     /// </summary> 13     public class OpcDaCustomItem
 14     {
 15         private string name;
 16         private string accessPath="";
 17         private string itemID;
 18         private int isActive = 1;
 19         private int clientHandle = 0;
 20         private int blobSize = 0;
 21         private IntPtr blob = IntPtr.Zero;
 22         private short requestedDataType = 0;
 23         private object itemValue;
 24         private int serverHandle;
 25 26         /// <summary> 27         /// 项名称 28         /// </summary> 29         public string Name
 30         {
 31             get 32             {
 33                 return name;
 34             }
 35             set 36             {
 37                 if (name == value)
 38                     return;
 39                 name = value;
 40             }
 41         }
 42         /// <summary> 43         /// 项对象的访问路径 44         /// </summary> 45         public string AccessPath
 46         {
 47             get 48             {
 49                 return accessPath;
 50             }
 51             set 52             {
 53                 if (accessPath == value)
 54                     return;
 55                 accessPath = value;
 56             }
 57         }
 58 59         /// <summary> 60         /// 项对象的ItemIDea,唯一标识该数据项 61         /// </summary> 62         public string ItemID
 63         {
 64             get 65             {
 66                 return itemID;
 67             }
 68             set 69             {
 70                 if (itemID == value)
 71                     return;
 72                 itemID = value;
 73             }
 74         }
 75 76         /// <summary> 77         /// 项对象的激活状态 78         /// 1为激活,0为未激活,默认激活 79         /// </summary> 80         public int IsActive
 81         {
 82             get 83             {
 84                 return isActive;
 85             }
 86             set 87             {
 88                 if (isActive == value)
 89                     return;
 90                 isActive = value;
 91             }
 92         }
 93 94         /// <summary> 95         /// 项对象的客户端句柄 96         /// </summary> 97         public int ClientHandle
 98         {
 99             get100             {
101                 return clientHandle;
102             }
103             set104             {
105                 if (clientHandle == value)
106                     return;
107                 clientHandle = value;
108             }
109         }
110         public int BlobSize
111         {
112             get113             {
114                 return blobSize;
115             }
116             set117             {
118                 if (blobSize == value)
119                     return;
120                 blobSize = value;
121             }
122         }
123         public IntPtr Blob
124         {
125             get126             {
127                 return blob;
128             }
129             set130             {
131                 if (blob == value)
132                     return;
133                 blob = value;
134             }
135         }
136137         /// <summary>138         /// OPC项的数据类型139         /// VbBoolean:11,VbByte:17,VbDecimal:14,VbDouble:5,Vbinteger:2,VbLong:3,VbSingle:4,VbString:8140         /// </summary>141         public short RequestedDataType
142         {
143             get144             {
145                 return requestedDataType;
146             }
147             set148             {
149                 if (requestedDataType == value)
150                     return;
151                 requestedDataType = value;
152             }
153         }
154155        /// <summary>156        /// OPC项的值157        /// </summary>158         public object Value
159         {
160             get161             {
162                 return itemValue;
163             }
164             set165             {
166                 if (itemValue == value)
167                     return;
168                 itemValue = value;
169             }
170         }
171172         /// <summary>173         /// OPC项的服务器句柄174         /// </summary>175         public int ServerHandle
176         {
177             get178             {
179                 return serverHandle;
180             }
181             set182             {
183                 if (serverHandle == value)
184                     return;
185                 serverHandle = value;
186             }
187         }
188     }
189 }

项的客户端句柄和服务器句柄实际是一样的,项的数据类型用short表示,在下面的配置文件中体现出来了。

以下是我设计的配置文件:

 1 <?xml version="1.0" encoding="utf-8"?> 2 <System> 3   <OpcServer ServerName="OPC.SimaticNET" IPAddress="10.102.102.118"> 4     <!--采煤机参数--> 5     <ShearerInfo GroupName="ShearerInfoGroup" ClientHandle="1" UpdateRate="100"> 6       <!--左牵,1表示左牵,0表示未运动--> 7       <Item ItemID="S7:[S7 connection_2]DB201,X20.2" ClientHandle="1" RequestedDataType="11"></Item> 8       <!--右牵,1表示右牵,0表示未运动--> 9       <Item ItemID="S7:[S7 connection_2]DB201,X20.1" ClientHandle="2" RequestedDataType="11"></Item>10       <!--牵引速度-->11       <Item ItemID="S7:[S7 connection_2]DB201,REAL40" ClientHandle="3" RequestedDataType="5"></Item>12       <!--采煤机位置-->13       <Item ItemID="S7:[S7 connection_2]DB201,REAL44" ClientHandle="4" RequestedDataType="5"></Item>14       <!--左滚筒高度-->15       <Item ItemID="S7:[S7 connection_2]DB201,REAL48" ClientHandle="5" RequestedDataType="5"></Item>16       <!--右滚筒高度-->17       <Item ItemID="S7:[S7 connection_2]DB201,REAL52" ClientHandle="6" RequestedDataType="5"></Item>18       <!--左截电流-->19       <Item ItemID="S7:[S7 connection_2]DB201,INT6" ClientHandle="7" RequestedDataType="2"></Item>20       <!--右截电流-->21       <Item ItemID="S7:[S7 connection_2]DB201,INT8" ClientHandle="8" RequestedDataType="2"></Item>22       <!--左牵电流-->23       <Item ItemID="S7:[S7 connection_2]DB201,INT2" ClientHandle="9" RequestedDataType="2"></Item>24       <!--右牵电流-->25       <Item ItemID="S7:[S7 connection_2]DB201,INT4" ClientHandle="10" RequestedDataType="2"></Item>26       <!--左截启-->27       <Item ItemID="S7:[S7 connection_2]DB201,X20.6" ClientHandle="11" RequestedDataType="11"></Item>28       <!--右截启-->29       <Item ItemID="S7:[S7 connection_2]DB201,X20.5" ClientHandle="12" RequestedDataType="11"></Item>30       <!--左截温度-->31       <Item ItemID="S7:[S7 connection_2]DB201,INT10" ClientHandle="13" RequestedDataType="2"></Item>32       <!--右截温度-->33       <Item ItemID="S7:[S7 connection_2]DB201,INT12" ClientHandle="14" RequestedDataType="2"></Item>34       <!--油泵电机电流-->35       <Item ItemID="S7:[S7 connection_2]DB201,INT14" ClientHandle="15" RequestedDataType="2"></Item>36       <!--工作模式 2人工 4学习 8自动割煤 16 传感器配置-->37       <Item ItemID="S7:[S7 connection_2]DB201,INT34" ClientHandle="16" RequestedDataType="2"></Item>38     </ShearerInfo>39   </OpcServer>40 </System>

上述配置文件中,OpcServer节点对应的OpcServer对象,定义了ServerName和IPAddress属性,用来连接OPCServer。

ShearerInfo节点则对应一个OpcGroup,在OpcServer下定义多个OPCGrupo节点,OPCGroup节点需要指定组的客户端句柄和刷新频率。上文说到OPC的读写操作都是以组进行的,我们需要根据客户端句柄来判断是哪一个组,如果我们采用的事订阅模式读取数据,则还需要刷新频率,OpcServer对订阅模式的实现不太清楚,实际使用的过程发现,并没有按照刷新频率来,所以我就采用了直接读的方式来保证数据的实时性。

Item的ItemID是一个地址,由于我使用的是西门子的产品,所以格式是:S7:[S7连接名称]地址,我们只需要更改S7连接的名称和地址就好了。如果你使用的事其他类型的PLC,请参照他们的地址格式。

有了配置文件如何操作呢?下面我定义了一个实现类:

  1 using System;
  2 using System.Collections.Generic;
  3 using System.Linq;
  4 using System.IO;
  5 using System.Runtime.InteropServices;
  6 using System.Xml.Linq;
  7  8 namespace Opc.Net
  9 {
 10     public class OpcManager
 11     {
 12         /// <summary> 13         /// Opc异步接口类 14         /// </summary> 15         OpcDaCustomAsync _opcDaCustomAsync;
 16         /// <summary> 17         /// 异步读取数据完成事件 18         /// </summary> 19         public event EventHandler<OpcDaCustomAsyncEventArgs> OnReadCompleted;
 20         /// <summary> 21         /// Opc组列表 22         /// </summary> 23         List<OpcDaCustomGroup> _opcGroups;
 24         /// <summary> 25         /// OPC服务器名称 26         /// </summary> 27         string _strRemoteServerName;
 28         /// <summary> 29         /// OPC服务器IP地址 30         /// </summary> 31         string _strRemoteServerIpAddress;
 32 33         /// <summary> 34         /// 构造函数 35         /// </summary> 36         /// <param name="strConfigFilePath">配置文件路径</param> 37         public OpcManager(string strConfigFilePath)
 38         {
 39             LoadOpcGroupConfig(strConfigFilePath);
 40         }
 41         /// <summary> 42         /// 加载Opc组配置 43         /// </summary> 44         /// <param name="strConfigFilePath">配置文件路径</param> 45         public void LoadOpcGroupConfig(string strConfigFilePath)
 46         {
 47             try 48             {
 49                 if (!File.Exists(strConfigFilePath)) return;
 50                 XDocument xDoc = XDocument.Load(strConfigFilePath);
 51                 XElement xElement = xDoc.Element("System").Element("OpcServer");
 52                 _strRemoteServerName = xElement.Attribute("ServerName").Value;
 53                 _strRemoteServerIpAddress = xElement.Attribute("IPAddress").Value;
 54                 _opcGroups = new List<OpcDaCustomGroup>();
 55                 foreach (XElement xElementItem in xElement.Elements())
 56                 {
 57                     var opcDaCustomGroupService = new OpcDaCustomGroup
 58                     {
 59                         GroupName = xElementItem.Attribute("GroupName").Value,
 60                         ClientGroupHandle = Convert.ToInt32(xElementItem.Attribute("ClientHandle").Value),
 61                         RequestedUpdateRate = Convert.ToInt32(xElementItem.Attribute("UpdateRate").Value),
 62                         OpcDataCustomItems = LoadOpcItemConfig(xElementItem)
 63                     };
 64                     _opcGroups.Add(opcDaCustomGroupService);
 65                 }
 66                 _opcDaCustomAsync = new OpcDaCustomAsync(_opcGroups, _strRemoteServerName, _strRemoteServerIpAddress);
 67                 _opcDaCustomAsync.OnReadCompleted += ReadCompleted;
 68             }
 69             catch(COMException ex)
 70             {
 71                 throw ex;
 72             }
 73         }
 74         /// <summary> 75         /// 连接Opc服务器 76         /// </summary> 77         /// <returns></returns> 78         public bool Connect()
 79         {
 80             return _opcDaCustomAsync.Connect();
 81         }
 82         /// <summary> 83         /// 连接Opc服务器 84         /// </summary> 85         /// <returns></returns> 86         public bool Connect(string remoteOpcServerName,string remoteOpcServerIpAddress)
 87         {
 88             return _opcDaCustomAsync.Connect(remoteOpcServerName, remoteOpcServerIpAddress);
 89         }
 90         /// <summary> 91         /// 加载Opc项配置 92         /// </summary> 93         /// <param name="xElement">Opc组Xml节点</param> 94         /// <returns></returns> 95         public OpcDaCustomItem[] LoadOpcItemConfig(XElement xElement)
 96         {
 97             int itemCount = xElement.Elements().Count();
 98             var opcDaCustomItems = new OpcDaCustomItem[itemCount];
 99             int i = 0;
100             foreach (var xElementItem in xElement.Elements())
101             {
102                 var opcDaCustomItemService = new OpcDaCustomItem
103                 {
104                     ClientHandle = Convert.ToInt32(xElementItem.Attribute("ClientHandle").Value),
105                     ItemID = xElementItem.Attribute("ItemID").Value,
106                     RequestedDataType = short.Parse(xElementItem.Attribute("RequestedDataType").Value)
107                 };
108                 opcDaCustomItems[i] = opcDaCustomItemService;
109                 i++;
110             }
111             return opcDaCustomItems;
112         }
113         public bool WriteForReturn(int itemClientHandle, int value, int clientHandle)
114         {
115             bool returnValue;
116             var itemDictionary = new Dictionary<int, object>
117             {
118                 {itemClientHandle, value}
119             };
120             try121             {
122                 int[] pErrors;
123                 Write(itemDictionary, clientHandle, out pErrors);
124                 returnValue = (pErrors[0] == 0);
125             }
126             catch (COMException ex)
127             {
128                 throw ex;
129             }
130             return returnValue;
131         }
132         public void Write(Dictionary<int, object> itemDictionary, int groupHandle, out int[] pErrors)
133         {
134             var count = itemDictionary.Count();
135             var values = new object[count];
136             var serverHandle = new int[count];
137             pErrors = null;
138             OpcDaCustomGroup group = _opcGroups.First(p => p.ServerGroupHandle == groupHandle);
139             int index = 0;
140             foreach (KeyValuePair<int, object> itemId in itemDictionary)
141             {
142                 foreach (var item in group.OpcDataCustomItems)
143                 {
144                     if (item.ClientHandle == itemId.Key)
145                     {
146                         values[index] = itemId.Value;
147                         serverHandle[index] = item.ServerHandle;
148                         index++;
149                     }
150                 }
151             }
152             try153             {
154                 _opcDaCustomAsync.Write(values, serverHandle, out pErrors, group);
155             }
156             catch (COMException ex)
157             {
158                 throw ex;
159             }
160         }
161         /// <summary>162         /// 写单个数据163         /// </summary>164         /// <param name="value"></param>165         /// <param name="groupHandle">组ID</param>166         /// <param name="clientHandle">项ID</param>167         public void Write(int value, int groupHandle, int clientHandle)
168         {
169             OpcDaCustomGroup group = GetOpcGroup(groupHandle);
170             if (group != null)
171             {
172                 int[] pErrors;
173                 var serverHanlde = new int[1];
174                 serverHanlde[0] = group.OpcDataCustomItems.First(c => c.ClientHandle == clientHandle).ServerHandle;
175                 var values = new object[1];
176                 values[0] = value;
177178                 _opcDaCustomAsync.Write(values, serverHanlde, out pErrors, group);
179180             }
181         }
182         /// <summary>183         /// 异步读取数据完成事件184         /// </summary>185         /// <param name="sender"></param>186         /// <param name="e"></param>187         public void ReadCompleted(object sender, OpcDaCustomAsyncEventArgs e)
188         {
189             if (OnReadCompleted != null)
190             {
191                 OnReadCompleted(this, e);
192             }
193         }
194         /// <summary>195         /// 异步读取控制模式数据196         /// </summary>197         public void Read()
198         {
199             if (_opcDaCustomAsync != null)
200             {
201                 _opcDaCustomAsync.Read();
202             }
203204         }
205         /// <summary>206         /// 根据OPC句柄获取OPC组对象207         /// </summary>208         /// <param name="groupHandle">OPC组对象</param>209         /// <returns></returns>210         public OpcDaCustomGroup GetOpcGroup(int groupHandle)
211         {
212             return _opcGroups.First(e => e.ClientGroupHandle == groupHandle);
213         }
214     }
215 }

View Code

这个类可以根据自己设计的配置文件进行相应的实现。

 1 private OpcManager opcManager;
 2         private System.Timers.Timer opcTimer;
 3         private int[] pErrors;
 4         private Dictionary<int, object> items;
 5         /// <summary> 6         /// 写入采煤机位置数据 7         /// </summary> 8         /// <param name="sender"></param> 9         /// <param name="e"></param>10         private void button1_Click(object sender, EventArgs e)
11         {
12             items = new Dictionary<int, object>();
13             items.Add(2, textBox2.Text);
14             opcManager.Write(items, 1, pErrors);
15         }
1617         private void FrmMain_Load(object sender, EventArgs e)
18         {
19             opcManager = new OpcManager(AppDomain.CurrentDomain.BaseDirectory+"\\Opc.config.xml");
20             opcManager.OnReadCompleted += new EventHandler<OpcDaCustomAsyncEventArgs>(opcManager_OnReadCompleted);
2122             opcTimer = new System.Timers.Timer()
23             {
24                 Interval = 100,
25                 AutoReset = true,
26                 Enabled = true27             };
28             opcTimer.Elapsed += new ElapsedEventHandler(opcTimer_Elapsed);
29         }
3031         void opcTimer_Elapsed(object sender, ElapsedEventArgs e)
32         {
33             opcManager.Read();
34         }
3536         void opcManager_OnReadCompleted(object sender, OpcDaCustomAsyncEventArgs e)
37         {
38             Invoke((ThreadStart)(() =>
39             {
4041                 if (OpcHelper.ShowValue(e, 3) != null)
42                 {
43                     textBox1.Text = OpcHelper.ShowValue(e, 3).ToString();
44                 }
45             }));
46         }

以上实现了数据的读取和写入。

附件下载源码

附件列表