【干货】如何通过OPC自定义接口来实现客户端数据的读取?

  上篇博文分享了我的知识库,被好多人关注,受宠若惊。今天我把我在项目中封装的OPC自定义接口的程序分享一下。下面将会简单简单介绍下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,下图是他们之间的逻辑关系:

  【干货】如何通过OPC自定义接口来实现客户端数据的读取?_第1张图片

 

  在客户端开发时,要使用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进行配置,如下图:

    【干货】如何通过OPC自定义接口来实现客户端数据的读取?_第2张图片

    首先我们要指定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)的软件,打开如下图:

    【干货】如何通过OPC自定义接口来实现客户端数据的读取?_第3张图片

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

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

    【干货】如何通过OPC自定义接口来实现客户端数据的读取?_第4张图片

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

    【干货】如何通过OPC自定义接口来实现客户端数据的读取?_第5张图片

    上图列出了名为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     /// 
 10     /// Opc自定义接口-异步管理类
 11     /// 
 12     /// 
 13     public class OpcDaCustomAsync : IOPCDataCallback,IDisposable
 14     {
 15         /// 
 16         /// OPC服务器对象
 17         /// 
 18         IOPCServer iOpcServer;
 19         /// 
 20         /// 事务ID
 21         /// 
 22         int transactionID;
 23         /// 
 24         /// OPC服务器名称
 25         /// 
 26         string opcServerName;
 27         /// 
 28         /// OPC服务器IP地址
 29         /// 
 30         IOPCAsyncIO2 _iopcAsyncIo2;
 31         /// 
 32         /// OPC服务器IP地址
 33         /// 
 34         string opcServerIPAddress;
 35         /// 
 36         /// Opc组列表
 37         /// 
 38         List opcDaCustomGroups;
 39         /// 
 40         /// 连接指针容器
 41         /// 
 42         IConnectionPointContainer IConnectionPointContainer = null;
 43         /// 
 44         /// 连接指针
 45         /// 
 46         IConnectionPoint IConnectionPoint = null;
 47         /// 
 48         /// Opc组管理器
 49         /// 
 50         IOPCGroupStateMgt IOPCGroupStateMgt = null;
 51 
 52 
 53         //接收数据事件
 54         public event EventHandler OnDataChanged;
 55         /// 
 56         /// 异步写入数据完成事件
 57         /// 
 58         public event EventHandler OnWriteCompleted;
 59         /// 
 60         /// 异步读取数据完成事件
 61         /// 
 62         public event EventHandler OnReadCompleted;
 63 
 64         /// 
 65         /// 构造函数
 66         /// 
 67         /// Opc组列表
 68         /// OPC服务器名称
 69         /// OPC服务器IP地址
 70         public OpcDaCustomAsync(List opcDaCustomGroups, string opcServerName, string opcServerIpAddress)
 71         {
 72             this.opcDaCustomGroups = opcDaCustomGroups;
 73             this.opcServerName = opcServerName;
 74             this.opcServerIPAddress = opcServerIpAddress;
 75             Init();
 76         }
 77         /// 
 78         /// 初始化参数
 79         /// 
 80         public void Init()
 81         {
 82             if (Connect())
 83             {
 84                 AddOpcGroup();
 85             }
 86         }
 87 
 88         /// 
 89         /// 连接Opc服务器
 90         /// 
 91         /// 
 92         public bool Connect()
 93         {
 94             return Connect(opcServerName, opcServerIPAddress);
 95         }
 96         /// 
 97         /// 连接Opc服务器
 98         /// 
 99         /// 
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         /// 
115         /// 添加Opc组
116         /// 
117         private void AddOpcGroup()
118         {
119             try
120             {
121                 foreach (OpcDaCustomGroup opcGroup in opcDaCustomGroups)
122                 {
123                     AddOpcGroup(opcGroup);
124                 }
125             }
126             catch(COMException ex)
127             {
128                 throw ex;
129             }
130         }
131         /// 
132         /// 添加Opc项
133         /// 
134         /// 
135         private void AddOpcGroup(OpcDaCustomGroup opcGroup)
136         {
137             try
138             {
139 
140                 //添加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             finally
156             {
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         /// 
168         /// 初始化IO接口
169         /// 
170         /// 
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, out 
181                     cookie);
182         }
183         /// 
184         /// 激活订阅回调事件
185         /// 
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             try
196             {
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             finally
207             {
208                 hActive.Free();
209             }
210         }
211 
212         /// 
213         /// 添加Opc项
214         /// 
215         /// 
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             try
226             {
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                     }
240 
241                 }
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             finally
265             {
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         /// 
277         /// 异步读取信息
278         /// 
279         public void Read()
280         {
281             foreach (OpcDaCustomGroup opcGroup in opcDaCustomGroups)
282             {
283                 IntPtr pErrors = IntPtr.Zero;
284                 try
285                 {
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                 finally
305                 {
306                     if (pErrors != IntPtr.Zero)
307                     {
308                         Marshal.FreeCoTaskMem(pErrors);
309                     }
310                 }
311             }
312         }
313 
314         /// 
315         /// 异步写入数据
316         /// 
317         /// 要写入的值
318         /// 要写入的项的服务器句柄
319         /// 错误信息,等于表示写入成功,否则写入失败
320         /// 要写入的Opc组
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                 try
329                 {
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                 finally
340                 {
341                     if (pErrors != IntPtr.Zero)
342                     {
343                         Marshal.FreeCoTaskMem(pErrors);
344                     }
345                 }
346             }
347         }
348         /// 
349         /// 数据订阅事件
350         /// 
351         /// 
352         /// 
353         /// 
354         /// 
355         /// 
356         /// 
357         /// 
358         /// 
359         /// 
360         /// 
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)
371         
372         {
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         }
386 
387         /// 
388         /// 取消事件
389         /// 
390         /// 
391         /// 
392         public virtual void OnCancelComplete(Int32 dwTransid, Int32 hGroup)
393         {
394 
395         }
396 
397         /// 
398         /// 写入数据完成事件
399         /// 
400         /// 
401         /// 
402         /// 
403         /// 
404         /// 
405         /// 
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         /// 
426         /// 读取数据完成事件
427         /// 
428         /// 
429         /// 
430         /// 
431         /// 
432         /// 要读取的组的项的个数
433         /// 
434         /// 项值列表
435         /// 
436         /// 
437         /// 项错误列表
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         {
464 
465         }
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     /// 
  8     /// 自定义接口OPC组对象
  9     /// 
 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         /// 
 23         /// 输出参数,服务器为新创建的组对象产生的句柄
 24         /// 
 25         public int ServerGroupHandle;
 26 
 27         /// 
 28         /// 输出参数,服务器返回给客户端的实际使用的数据更新率
 29         /// 
 30         public int RevisedUpdateRate;
 31 
 32         /// 
 33         /// 引用参数,客户端想要的组对象的接口类型(如 IIDIOPCItemMgt)
 34         /// 
 35         public Guid Riid = typeof(IOPCItemMgt).GUID;
 36 
 37         /// 
 38         /// 输出参数,用来存储返回的接口指针。如果函数操作出现任务失败,此参数将返回NULL。
 39         /// 
 40         public object Group;
 41         private OpcDaCustomItem[] opcDataCustomItems;
 42 
 43         public int[] PErrors { get; set; }
 44 
 45         /// 
 46         /// 组对象是否激活
 47         /// 1为激活,0为未激活,默认激活
 48         /// 
 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         /// 
 63         /// 组是否采用异步读方式
 64         /// 
 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         /// 
 79         /// 项的个数
 80         /// 
 81         public int ItemCount
 82         {
 83             get { return itemCount; }
 84             set 
 85             {
 86                 if(itemCount == value)
 87                     return;
 88                 itemCount=value;
 89             }
 90         }
 91         /// 
 92         /// 客户端指定的数据变化率
 93         /// 
 94         public int RequestedUpdateRate
 95         {
 96             get
 97             {
 98                 return requestedUpdateRate;
 99             }
100             set
101             {
102                 if (requestedUpdateRate == value)
103                     return;
104                 requestedUpdateRate = value;
105             }
106         }
107 
108         /// 
109         /// OPC组名称
110         /// 
111         public string GroupName
112         {
113             get
114             {
115                 return groupName;
116             }
117             set
118             {
119                 if (groupName == value)
120                     return;
121                 groupName = value;
122             }
123         }
124 
125         /// 
126         /// 客户端程序为组对象提供的句柄
127         /// 
128         public int ClientGroupHandle
129         {
130             get
131             {
132                 return clientGroupHandle;
133             }
134             set
135             {
136                 if (clientGroupHandle == value)
137                     return;
138                 clientGroupHandle = value;
139             }
140         }
141 
142         /// 
143         /// 指向Long类型的指针
144         /// 
145         public GCHandle TimeBias
146         {
147             get
148             {
149                 return timeBias;
150             }
151             set
152             {
153                 if (timeBias == value)
154                     return;
155                 timeBias = value;
156             }
157         }
158 
159         /// 
160         /// 一个项对象的值变化的百分比,可能引发客户端程序的订阅回调。
161         /// 此参数只应用于组对象中有模拟dwEUType(工程单位)类型的项对象。指针为NULL表示0.0
162         /// 
163         public GCHandle PercendDeadBand
164         {
165             get
166             {
167                 return percendDeadBand;
168             }
169             set
170             {
171                 if (percendDeadBand == value)
172                     return;
173                 percendDeadBand = value;
174             }
175         }
176 
177         /// 
178         /// 当用于组对象上的操作的返回值为文本类型时,服务器使用的语言
179         /// 
180         public int LCID
181         {
182             get
183             {
184                 return lcid;
185             }
186             set
187             {
188                 if (lcid == value)
189                     return;
190                 lcid = value;
191             }
192         }
193 
194         /// 
195         /// OPC项数组
196         /// 
197         public OpcDaCustomItem[] OpcDataCustomItems
198         {
199             get
200             {
201                 return opcDataCustomItems;
202             }
203             set
204             {
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     /// 
 11     /// 自定义接口Opc项
 12     /// 
 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         /// 
 27         /// 项名称
 28         /// 
 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         /// 
 43         /// 项对象的访问路径
 44         /// 
 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         /// 
 60         /// 项对象的ItemIDea,唯一标识该数据项
 61         /// 
 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         /// 
 77         /// 项对象的激活状态
 78         /// 1为激活,0为未激活,默认激活
 79         /// 
 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         /// 
 95         /// 项对象的客户端句柄
 96         /// 
 97         public int ClientHandle
 98         {
 99             get
100             {
101                 return clientHandle;
102             }
103             set
104             {
105                 if (clientHandle == value)
106                     return;
107                 clientHandle = value;
108             }
109         }
110         public int BlobSize
111         {
112             get
113             {
114                 return blobSize;
115             }
116             set
117             {
118                 if (blobSize == value)
119                     return;
120                 blobSize = value;
121             }
122         }
123         public IntPtr Blob
124         {
125             get
126             {
127                 return blob;
128             }
129             set
130             {
131                 if (blob == value)
132                     return;
133                 blob = value;
134             }
135         }
136 
137         /// 
138         /// OPC项的数据类型
139         /// VbBoolean:11,VbByte:17,VbDecimal:14,VbDouble:5,Vbinteger:2,VbLong:3,VbSingle:4,VbString:8
140         /// 
141         public short RequestedDataType
142         {
143             get
144             {
145                 return requestedDataType;
146             }
147             set
148             {
149                 if (requestedDataType == value)
150                     return;
151                 requestedDataType = value;
152             }
153         }
154 
155        /// 
156        /// OPC项的值
157        /// 
158         public object Value
159         {
160             get
161             {
162                 return itemValue;
163             }
164             set
165             {
166                 if (itemValue == value)
167                     return;
168                 itemValue = value;
169             }
170         }
171 
172         /// 
173         /// OPC项的服务器句柄
174         /// 
175         public int ServerHandle
176         {
177             get
178             {
179                 return serverHandle;
180             }
181             set
182             {
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       
 7       <Item ItemID="S7:[S7 connection_2]DB201,X20.2" ClientHandle="1" RequestedDataType="11">Item>
 8       
 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       
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         /// 
 13         /// Opc异步接口类
 14         /// 
 15         OpcDaCustomAsync _opcDaCustomAsync;
 16         /// 
 17         /// 异步读取数据完成事件
 18         /// 
 19         public event EventHandler OnReadCompleted;
 20         /// 
 21         /// Opc组列表
 22         /// 
 23         List _opcGroups;
 24         /// 
 25         /// OPC服务器名称
 26         /// 
 27         string _strRemoteServerName;
 28         /// 
 29         /// OPC服务器IP地址
 30         /// 
 31         string _strRemoteServerIpAddress;
 32 
 33         /// 
 34         /// 构造函数
 35         /// 
 36         /// 配置文件路径
 37         public OpcManager(string strConfigFilePath)
 38         {
 39             LoadOpcGroupConfig(strConfigFilePath);
 40         }
 41         /// 
 42         /// 加载Opc组配置
 43         /// 
 44         /// 配置文件路径
 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();
 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         /// 
 75         /// 连接Opc服务器
 76         /// 
 77         /// 
 78         public bool Connect()
 79         {
 80             return _opcDaCustomAsync.Connect();
 81         }
 82         /// 
 83         /// 连接Opc服务器
 84         /// 
 85         /// 
 86         public bool Connect(string remoteOpcServerName,string remoteOpcServerIpAddress)
 87         {
 88             return _opcDaCustomAsync.Connect(remoteOpcServerName, remoteOpcServerIpAddress);
 89         }
 90         /// 
 91         /// 加载Opc项配置
 92         /// 
 93         /// Opc组Xml节点
 94         /// 
 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             try
121             {
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             try
153             {
154                 _opcDaCustomAsync.Write(values, serverHandle, out pErrors, group);
155             }
156             catch (COMException ex)
157             {
158                 throw ex;
159             }
160         }
161         /// 
162         /// 写单个数据
163         /// 
164         /// 
165         /// 组ID
166         /// 项ID
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;
177 
178                 _opcDaCustomAsync.Write(values, serverHanlde, out pErrors, group);
179 
180             }
181         }
182         /// 
183         /// 异步读取数据完成事件
184         /// 
185         /// 
186         /// 
187         public void ReadCompleted(object sender, OpcDaCustomAsyncEventArgs e)
188         {
189             if (OnReadCompleted != null)
190             {
191                 OnReadCompleted(this, e);
192             }
193         }
194         /// 
195         /// 异步读取控制模式数据
196         /// 
197         public void Read()
198         {
199             if (_opcDaCustomAsync != null)
200             {
201                 _opcDaCustomAsync.Read();
202             }
203 
204         }
205         /// 
206         /// 根据OPC句柄获取OPC组对象
207         /// 
208         /// OPC组对象
209         /// 
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         /// 
 6         /// 写入采煤机位置数据
 7         /// 
 8         /// 
 9         /// 
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         }
16 
17         private void FrmMain_Load(object sender, EventArgs e)
18         {
19             opcManager = new OpcManager(AppDomain.CurrentDomain.BaseDirectory+"\\Opc.config.xml");
20             opcManager.OnReadCompleted += new EventHandler(opcManager_OnReadCompleted);
21 
22             opcTimer = new System.Timers.Timer()
23             {
24                 Interval = 100,
25                 AutoReset = true,
26                 Enabled = true
27             };
28             opcTimer.Elapsed += new ElapsedEventHandler(opcTimer_Elapsed);
29         }
30 
31         void opcTimer_Elapsed(object sender, ElapsedEventArgs e)
32         {
33             opcManager.Read();
34         }
35 
36         void opcManager_OnReadCompleted(object sender, OpcDaCustomAsyncEventArgs e)
37         {
38             Invoke((ThreadStart)(() =>
39             {
40 
41                 if (OpcHelper.ShowValue(e, 3) != null)
42                 {
43                     textBox1.Text = OpcHelper.ShowValue(e, 3).ToString();
44                 }
45             }));
46         }

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

源码戳这里:http://pan.baidu.com/s/1ntp1JAx

转载于:https://www.cnblogs.com/code-ming/p/3848818.html

你可能感兴趣的:(【干货】如何通过OPC自定义接口来实现客户端数据的读取?)