【乾貨】如何經過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,下圖是他們之間的邏輯關係:服務器

  

 

  在客戶端開發時,要使用OpcServer對象來實現客戶端與Opc服務器之間的鏈接。一個OpcServer對象下有多個OpcGroup,一個OpcGroup下有多個OpcItem,在自定義接口下的Client開發,是以Group爲單位的操做,數據讀寫都是經過OpcGroup進行的。cookie

  •   搭建程序運行環境

    程序運行須要的軟硬件環境:app

    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選用的是西門子的。ide

    咱們能夠不寫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)的軟件,打開以下圖:ui

    

    上圖列出來了本機全部的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,請參照他們的地址格式。

有了配置文件如何操做呢?下面我定義了一個實現類:

  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             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         /// <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;
177 
178                 _opcDaCustomAsync.Write(values, serverHanlde, out pErrors, group);
179 
180             }
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             }
203 
204         }
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         }
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

相關文章
相關標籤/搜索