上篇博文分享了個人知識庫,被好多人關注,受寵若驚。今天我把我在項目中封裝的OPC自定義接口的程序分享一下。下面將會簡單簡單介紹下OPC DA客戶端數據訪問,以及搭配整個系統的運行環境。數組
使用OPC DA進行Client的讀寫操做時,咱們使用Custom接口,出此以外還有Automation接口。如下是Custome接口開發時涉及到的三個關鍵對象:OpcServer、OpcGroup、OpcItem,下圖是他們之間的邏輯關係:服務器
在客戶端開發時,要使用OpcServer對象來實現客戶端與Opc服務器之間的鏈接。一個OpcServer對象下有多個OpcGroup,一個OpcGroup下有多個OpcItem,在自定義接口下的Client開發,是以Group爲單位的操做,數據讀寫都是經過OpcGroup進行的。cookie
程序運行須要的軟硬件環境:app
咱們能夠經過本機的配置來實現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 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 }
這個類能夠根據本身設計的配置文件進行相應的實現。
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 }
以上實現了數據的讀取和寫入。