上篇博文分享了我的知识库,被好多人关注,受宠若惊。今天我把我在项目中封装的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,下图是他们之间的逻辑关系:
在客户端开发时,要使用OpcServer对象来实现客户端与Opc服务器之间的连接。一个OpcServer对象下有多个OpcGroup,一个OpcGroup下有多个OpcItem,在自定义接口下的Client开发,是以Group为单位的操作,数据读写都是通过OpcGroup进行的。
- 搭建程序运行环境
程序运行需要的软硬件环境:
-
- .Net Framework 4.0
- Simatic Net 2008(Or Other) HF1
- 西门子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 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 /// <summary>
132 /// 添加Opc项
133 /// </summary>
134 /// <param name="opcGroup"></param>
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 /// <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, out
181 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 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 /// <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 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 /// <summary>
277 /// 异步读取信息
278 /// </summary>
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 /// <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 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 /// <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)
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 /// <summary>
388 /// 取消事件
389 /// </summary>
390 /// <param name="dwTransid"></param>
391 /// <param name="hGroup"></param>
392 public virtual void OnCancelComplete(Int32 dwTransid, Int32 hGroup)
393 {
394
395 }
396
397 /// <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 {
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 /// <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 set
101 {
102 if (requestedUpdateRate == value)
103 return;
104 requestedUpdateRate = value;
105 }
106 }
107
108 /// <summary>
109 /// OPC组名称
110 /// </summary>
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 /// <summary>
126 /// 客户端程序为组对象提供的句柄
127 /// </summary>
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 /// <summary>
143 /// 指向Long类型的指针
144 /// </summary>
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 /// <summary>
160 /// 一个项对象的值变化的百分比,可能引发客户端程序的订阅回调。
161 /// 此参数只应用于组对象中有模拟dwEUType(工程单位)类型的项对象。指针为NULL表示0.0
162 /// </summary>
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 /// <summary>
178 /// 当用于组对象上的操作的返回值为文本类型时,服务器使用的语言
179 /// </summary>
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 /// <summary>
195 /// OPC项数组
196 /// </summary>
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 /// <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 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 /// <summary>
138 /// OPC项的数据类型
139 /// VbBoolean:11,VbByte:17,VbDecimal:14,VbDouble:5,Vbinteger:2,VbLong:3,VbSingle:4,VbString:8
140 /// </summary>
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 /// <summary>
156 /// OPC项的值
157 /// </summary>
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 /// <summary>
173 /// OPC项的服务器句柄
174 /// </summary>
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 <!--左牵,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,请参照他们的地址格式。
有了配置文件如何操作呢?下面我定义了一个实现类:
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 }
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<OpcDaCustomAsyncEventArgs>(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