上篇博文分享了我的知識庫,被好多人關(guān)注,受寵若驚。今天我把我在項目中封裝的OPC自定義接口的程序分享一下。下面將會簡單簡單介紹下OPC DA客戶端數(shù)據(jù)訪問,以及搭配整個系統(tǒng)的運行環(huán)境。 - OPC(OLE for Process Control)其實就是一套標準,我對這套標準理解不多,使用過程中就把它理解一套協(xié)議或者規(guī)范,主要用于工控領(lǐng)域。OPC中有很多規(guī)范,我主要使用OPC DA規(guī)范來進行數(shù)據(jù)的讀寫操作。還有其他規(guī)范,比如OPC UA、OPC HDA等。如果你做的是OPC Server開發(fā)查下這方面的資料了解下,這篇博文主要介紹OPC Client開發(fā)的知識。
使用OPC DA進行Client的讀寫操作時,我們使用Custom接口,出此之外還有Automation接口。以下是Custome接口開發(fā)時涉及到的三個關(guān)鍵對象:OpcServer、OpcGroup、OpcItem,下圖是他們之間的邏輯關(guān)系:  在客戶端開發(fā)時,要使用OpcServer對象來實現(xiàn)客戶端與Opc服務(wù)器之間的連接。一個OpcServer對象下有多個OpcGroup,一個OpcGroup下有多個OpcItem,在自定義接口下的Client開發(fā),是以Group為單位的操作,數(shù)據(jù)讀寫都是通過OpcGroup進行的。 程序運行需要的軟硬件環(huán)境: - .Net Framework 4.0
- Simatic Net 2008(Or Other) HF1
- 西門子300(Or Other) PLC
我們可以通過本機的配置來實現(xiàn)OPC的遠程連接,我沒有采用這種方式,一是這種配置比較麻煩,而是這種方式不穩(wěn)定。所以我采用本機安裝一個OPCServer來實現(xiàn)與PLC的交互。 對于OPCServer軟件,我選擇的是SimaticNet 2008 HF1(安裝WinCC的時候會有選擇安裝SimaticNet的選項),沒有特別的原因,就是比較熟悉了而已,而且PLC選用的是西門子的。 我們可以不寫OPC Client程序來測試,如何通過OPCServer與PLC之間的交互。首先當(dāng)我們安裝完畢SimaticNet之后,需要對Station Configuration Editor進行配置,如下圖:  首先我們要指定Station的名稱,上圖叫PCStation,點擊下方的StationName可以進行更改。下一步在1號棧上選擇一個OPCServer,3號棧上選擇一個通信網(wǎng)卡。 接下來我們需要在Step 7中建立Station Configuration Editor與PLC之間的連接,我們暫且叫組態(tài)。組態(tài)的過程中要建立與Station Configuration Editor中對應(yīng)的Opc Server和IE General(所在棧號相同),Station Configuration Edition起到橋接的作用 用,主要讓PLC與Opc Server之間建立一條S7連接。暫時沒有拿到組態(tài)圖,以后補上。 當(dāng)我們組態(tài)完畢時,如何判斷組態(tài)是否正確呢?在SimaticNet的目錄上有個叫Opc Scout(Opc Scout V10)的軟件,打開如下圖:  上圖列出來了本機所有的Server,我們能使用名為OPC.SimaticNET的Server。雙擊這個Server添加一個組,多次雙擊這個Server可以添加多個組,驗證了上圖的Server與Group的關(guān)系了。 我們雙擊新建的Group,進入如下圖的界面:  上圖列出了所有的連接。上文說到的組態(tài)中建立的S7連接可以在S7節(jié)點中看到,展開這個節(jié)點可以看到我們建立的S7連接,如下圖:  上圖列出了名為S7 connection_1的S7連接,展開Object對象,列出PLC的結(jié)構(gòu)。我們選擇一種來新建我們的Item,由于我這里沒有PLC模塊,所以無法截圖給大家看。 至此我們的OPC Client的運行環(huán)境搭建完畢。 我們需要使用OPC Foundation提供的自定義接口來進行開發(fā),在Visual Studio引用名為:OpcRcw.Comn.dll和OpcRcw.Da.dll這兩個DLL。 我們定義一個名為OpcDaCustomAsync的類,讓這個類繼承自:IOPCDataCallback,IDisposable  1 using System; 2 using System.Collections.Generic; 3 using OpcRcw.Comn; 4 using OpcRcw.Da; 5 using System.Runtime.InteropServices; 6 7 namespace Opc.Net 8 { 9 /// 10 /// Opc自定義接口-異步管理類 11 /// 12 /// 13 public class OpcDaCustomAsync : IOPCDataCallback,IDisposable 14 { 15 /// 16 /// OPC服務(wù)器對象 17 /// 18 IOPCServer iOpcServer; 19 /// 20 /// 事務(wù)ID 21 /// 22 int transactionID; 23 /// 24 /// OPC服務(wù)器名稱 25 /// 26 string opcServerName; 27 /// 28 /// OPC服務(wù)器IP地址 29 /// 30 IOPCAsyncIO2 _iopcAsyncIo2; 31 /// 32 /// OPC服務(wù)器IP地址 33 /// 34 string opcServerIPAddress; 35 /// 36 /// Opc組列表 37 /// 38 List opcDaCustomGroups; 39 /// 40 /// 連接指針容器 41 /// 42 IConnectionPointContainer IConnectionPointContainer = null; 43 /// 44 /// 連接指針 45 /// 46 IConnectionPoint IConnectionPoint = null; 47 /// 48 /// Opc組管理器 49 /// 50 IOPCGroupStateMgt IOPCGroupStateMgt = null; 51 52 53 //接收數(shù)據(jù)事件 54 public event EventHandler OnDataChanged; 55 /// 56 /// 異步寫入數(shù)據(jù)完成事件 57 /// 58 public event EventHandler OnWriteCompleted; 59 /// 60 /// 異步讀取數(shù)據(jù)完成事件 61 /// 62 public event EventHandler OnReadCompleted; 63 64 /// 65 /// 構(gòu)造函數(shù) 66 /// 67 /// Opc組列表 68 /// OPC服務(wù)器名稱 69 /// OPC服務(wù)器IP地址 70 public OpcDaCustomAsync(List opcDaCustomGroups, string opcServerName, string opcServerIpAddress) 71 { 72 this.opcDaCustomGroups = opcDaCustomGroups; 73 this.opcServerName = opcServerName; 74 this.opcServerIPAddress = opcServerIpAddress; 75 Init(); 76 } 77 /// 78 /// 初始化參數(shù) 79 /// 80 public void Init() 81 { 82 if (Connect()) 83 { 84 AddOpcGroup(); 85 } 86 } 87 88 /// 89 /// 連接Opc服務(wù)器 90 /// 91 /// 92 public bool Connect() 93 { 94 return Connect(opcServerName, opcServerIPAddress); 95 } 96 /// 97 /// 連接Opc服務(wù)器 98 /// 99 /// 100 public bool Connect(string remoteOpcServerName, string remoteOpcServerIpAddress)101 {102 var returnValue = false;103 if (!string.IsNullOrEmpty(remoteOpcServerIpAddress) && !string.IsNullOrEmpty(remoteOpcServerName))104 {105 var opcServerType = Type.GetTypeFromProgID(remoteOpcServerName, remoteOpcServerIpAddress);106 if (opcServerType != null)107 {108 iOpcServer = (IOPCServer)Activator.CreateInstance(opcServerType);109 returnValue = true;110 }111 } 112 return returnValue;113 }114 /// 115 /// 添加Opc組116 /// 117 private void AddOpcGroup()118 {119 try120 {121 foreach (OpcDaCustomGroup opcGroup in opcDaCustomGroups)122 {123 AddOpcGroup(opcGroup);124 }125 }126 catch(COMException ex)127 {128 throw ex;129 }130 }131 /// 132 /// 添加Opc項133 /// 134 /// 135 private void AddOpcGroup(OpcDaCustomGroup opcGroup)136 {137 try138 {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 //激活訂閱回調(diào)事件148 ActiveDataChanged(IOPCGroupStateMgt);149 }150 }151 catch (COMException ex)152 {153 throw ex;154 }155 finally156 {157 if (opcGroup.TimeBias.IsAllocated)158 {159 opcGroup.TimeBias.Free();160 }161 if (opcGroup.PercendDeadBand.IsAllocated)162 {163 opcGroup.PercendDeadBand.Free();164 }165 }166 }167 /// 168 /// 初始化IO接口169 /// 170 /// 171 public void InitIoInterfaces(OpcDaCustomGroup opcGroup)172 {173 int cookie;174 //組狀態(tài)管理對象,改變組的刷新率和激活狀態(tài)175 IOPCGroupStateMgt = (IOPCGroupStateMgt)opcGroup.Group;176 IConnectionPointContainer = (IConnectionPointContainer)opcGroup.Group;177 Guid iid = typeof(IOPCDataCallback).GUID;178 IConnectionPointContainer.FindConnectionPoint(ref iid, out IConnectionPoint);179 //創(chuàng)建客戶端與服務(wù)端之間的連接180 IConnectionPoint.Advise(this, out 181 cookie);182 }183 /// 184 /// 激活訂閱回調(diào)事件185 /// 186 private void ActiveDataChanged(IOPCGroupStateMgt IOPCGroupStateMgt)187 {188 IntPtr pRequestedUpdateRate = IntPtr.Zero;189 IntPtr hClientGroup = IntPtr.Zero;190 IntPtr pTimeBias = IntPtr.Zero;191 IntPtr pDeadband = IntPtr.Zero;192 IntPtr pLCID = IntPtr.Zero;193 int nActive = 0;194 GCHandle hActive = GCHandle.Alloc(nActive, GCHandleType.Pinned);195 try196 {197 hActive.Target = 1;198 int nRevUpdateRate = 0;199 IOPCGroupStateMgt.SetState(pRequestedUpdateRate, out nRevUpdateRate,200 hActive.AddrOfPinnedObject(), pTimeBias, pDeadband, pLCID, hClientGroup);201 }202 catch (COMException ex)203 {204 throw ex;205 }206 finally207 {208 hActive.Free();209 }210 }211 212 /// 213 /// 添加Opc項214 /// 215 /// 216 private void AddOpcItem(OpcDaCustomGroup opcGroup)217 {218 OpcDaCustomItem[] opcDataCustomItemsService = opcGroup.OpcDataCustomItems;219 IntPtr pResults = IntPtr.Zero;220 IntPtr pErrors = IntPtr.Zero;221 OPCITEMDEF[] itemDefyArray = new OPCITEMDEF[opcGroup.OpcDataCustomItems.Length];222 int i = 0;223 int[] errors = new int[opcGroup.OpcDataCustomItems.Length];224 int[] itemServerHandle = new int[opcGroup.OpcDataCustomItems.Length];225 try226 {227 foreach (OpcDaCustomItem itemService in opcDataCustomItemsService)228 {229 if (itemService != null)230 {231 itemDefyArray[i].szAccessPath = itemService.AccessPath;232 itemDefyArray[i].szItemID = itemService.ItemID;233 itemDefyArray[i].bActive = itemService.IsActive;234 itemDefyArray[i].hClient = itemService.ClientHandle;235 itemDefyArray[i].dwBlobSize = itemService.BlobSize;236 itemDefyArray[i].pBlob = itemService.Blob;237 itemDefyArray[i].vtRequestedDataType = itemService.RequestedDataType;238 i++;239 }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;="">)247 {248 if (errors[j] == 0)249 {250 if (j != 0)251 {252 Pos = new IntPtr(Pos.ToInt32() + Marshal.SizeOf(typeof(OPCITEMRESULT)));253 }254 var result = (OPCITEMRESULT)Marshal.PtrToStructure(Pos, typeof(OPCITEMRESULT));255 itemServerHandle[j] = opcDataCustomItemsService[j].ServerHandle = result.hServer;256 Marshal.DestroyStructure(Pos, typeof(OPCITEMRESULT));257 }258 }259 }260 catch (COMException ex)261 {262 throw ex;263 }264 finally265 {266 if (pResults != IntPtr.Zero)267 {268 Marshal.FreeCoTaskMem(pResults);269 }270 if (pErrors != IntPtr.Zero)271 {272 Marshal.FreeCoTaskMem(pErrors);273 }274 }275 }276 /// 277 /// 異步讀取信息278 /// 279 public void Read()280 {281 foreach (OpcDaCustomGroup opcGroup in opcDaCustomGroups)282 {283 IntPtr pErrors = IntPtr.Zero;284 try285 {286 _iopcAsyncIo2 = (IOPCAsyncIO2)opcGroup.Group;287 if (_iopcAsyncIo2 != null)288 {289 int[] serverHandle = new int[opcGroup.OpcDataCustomItems.Length];290 opcGroup.PErrors = new int[opcGroup.OpcDataCustomItems.Length];291 for (int j = 0; j < opcgroup.opcdatacustomitems.length;="">)292 {293 serverHandle[j] = opcGroup.OpcDataCustomItems[j].ServerHandle;294 }295 int cancelId=0;296 _iopcAsyncIo2.Read(opcGroup.OpcDataCustomItems.Length, serverHandle, 2, out cancelId, out pErrors);297 Marshal.Copy(pErrors, opcGroup.PErrors, 0, opcGroup.OpcDataCustomItems.Length);298 }299 }300 catch (COMException ex)301 {302 throw ex;303 }304 finally305 {306 if (pErrors != IntPtr.Zero)307 {308 Marshal.FreeCoTaskMem(pErrors);309 }310 }311 }312 }313 314 /// 315 /// 異步寫入數(shù)據(jù)316 /// 317 /// 要寫入的值318 /// 要寫入的項的服務(wù)器句柄319 /// 錯誤信息,等于表示寫入成功,否則寫入失敗320 /// 要寫入的Opc組321 public void Write(object[] values,int[] serverHandle,out int[] errors,OpcDaCustomGroup opcGroup)322 {323 _iopcAsyncIo2 = (IOPCAsyncIO2)opcGroup.Group;324 IntPtr pErrors = IntPtr.Zero;325 errors = new int[values.Length];326 if (_iopcAsyncIo2 != null)327 {328 try329 {330 //異步寫入數(shù)據(jù)331 int cancelId;332 _iopcAsyncIo2.Write(values.Length, serverHandle, values, transactionID + 1, out cancelId, out pErrors);333 Marshal.Copy(pErrors, errors, 0, values.Length);334 }335 catch (COMException ex)336 {337 throw ex;338 }339 finally340 {341 if (pErrors != IntPtr.Zero)342 {343 Marshal.FreeCoTaskMem(pErrors);344 }345 }346 }347 }348 /// 349 /// 數(shù)據(jù)訂閱事件350 /// 351 /// 352 /// 353 /// 354 /// 355 /// 356 /// 357 /// 358 /// 359 /// 360 /// 361 public virtual void OnDataChange(Int32 dwTransid,362 Int32 hGroup,363 Int32 hrMasterquality,364 Int32 hrMastererror,365 Int32 dwCount,366 int[] phClientItems,367 object[] pvValues,368 short[] pwQualities,369 OpcRcw.Da.FILETIME[] pftTimeStamps,370 int[] pErrors)371 372 {373 var e = new OpcDaCustomAsyncEventArgs374 {375 GroupHandle = hGroup,376 Count = dwCount,377 Errors = pErrors,378 Values = pvValues,379 ClientItemsHandle = phClientItems380 };381 if (OnDataChanged != null)382 {383 OnDataChanged(this, e);384 }385 }386 387 /// 388 /// 取消事件389 /// 390 /// 391 /// 392 public virtual void OnCancelComplete(Int32 dwTransid, Int32 hGroup)393 {394 395 }396 397 /// 398 /// 寫入數(shù)據(jù)完成事件399 /// 400 /// 401 /// 402 /// 403 /// 404 /// 405 /// 406 public virtual void OnWriteComplete(Int32 dwTransid,407 Int32 hGroup,408 Int32 hrMastererr,409 Int32 dwCount,410 int[] pClienthandles,411 int[] pErrors)412 {413 if (OnWriteCompleted != null)414 {415 var e = new OpcDaCustomAsyncEventArgs416 {417 Errors = pErrors418 };419 if (OnWriteCompleted != null)420 {421 OnWriteCompleted(this, e);422 }423 }424 }425 /// 426 /// 讀取數(shù)據(jù)完成事件427 /// 428 /// 429 /// 430 /// 431 /// 432 /// 要讀取的組的項的個數(shù)433 /// 434 /// 項值列表435 /// 436 /// 437 /// 項錯誤列表438 public virtual void OnReadComplete(Int32 dwTransid,439 Int32 hGroup,440 Int32 hrMasterquality,441 Int32 hrMastererror,442 Int32 dwCount,443 int[] phClientItems,444 object[] pvValues,445 short[] pwQualities,446 OpcRcw.Da.FILETIME[] pftTimeStamps,447 int[] pErrors)448 {449 if (OnReadCompleted != null)450 {451 var e = new OpcDaCustomAsyncEventArgs452 {453 GroupHandle = hGroup,454 Count = dwCount,455 Errors = pErrors,456 Values = pvValues,457 ClientItemsHandle = phClientItems458 };459 OnReadCompleted(this, e);460 }461 }462 public void Dispose()463 {464 465 }466 }467 }  我們看下IOPCDataCallback接口的定義: 
這個接口提供了4個函數(shù)。如果我們采用訂閱模式(默認的模式),會執(zhí)行OnDataChange函數(shù),主動讀數(shù)據(jù)則執(zhí)行OnReadComplete函數(shù),寫數(shù)據(jù)則執(zhí)行OnWriteComplete函數(shù)。在OpcDaCustomAsync類中,我已經(jīng)對這四個函數(shù)進行了實現(xiàn),每個實現(xiàn)對應(yīng)一個事件。 OpcDaCustomAsync類的實現(xiàn),我主要是扒了SimaticNet下的一個Sample,自己封裝了下。使用這個類的時候需要提供Group列表和OPCServer的名稱,以及OPCServer所在的主機的IP地址。 OpcGroup的封裝:  1 using System; 2 using System.Runtime.InteropServices; 3 using OpcRcw.Da; 4 5 namespace Opc.Net 6 { 7 /// 8 /// 自定義接口OPC組對象 9 /// 10 public class OpcDaCustomGroup 11 { 12 private string groupName; 13 private int isActive=1; 14 private int requestedUpdateRate; 15 private int clientGroupHandle=1; 16 private GCHandle timeBias = GCHandle.Alloc(0, GCHandleType.Pinned); 17 private GCHandle percendDeadBand = GCHandle.Alloc(0, GCHandleType.Pinned); 18 private int lcid = 0x409; 19 private int itemCount; 20 private bool onRead; 21 22 /// 23 /// 輸出參數(shù),服務(wù)器為新創(chuàng)建的組對象產(chǎn)生的句柄 24 /// 25 public int ServerGroupHandle; 26 27 /// 28 /// 輸出參數(shù),服務(wù)器返回給客戶端的實際使用的數(shù)據(jù)更新率 29 /// 30 public int RevisedUpdateRate; 31 32 /// 33 /// 引用參數(shù),客戶端想要的組對象的接口類型(如 IIDIOPCItemMgt) 34 /// 35 public Guid Riid = typeof(IOPCItemMgt).GUID; 36 37 /// 38 /// 輸出參數(shù),用來存儲返回的接口指針。如果函數(shù)操作出現(xiàn)任務(wù)失敗,此參數(shù)將返回NULL。 39 /// 40 public object Group; 41 private OpcDaCustomItem[] opcDataCustomItems; 42 43 public int[] PErrors { get; set; } 44 45 /// 46 /// 組對象是否激活 47 /// 1為激活,0為未激活,默認激活 48 /// 49 public int IsActive 50 { 51 get 52 { 53 return isActive; 54 } 55 set 56 { 57 if (isActive == value) 58 return; 59 isActive = value; 60 } 61 } 62 /// 63 /// 組是否采用異步讀方式 64 /// 65 public bool OnRead 66 { 67 get 68 { 69 return onRead; 70 } 71 set 72 { 73 if (onRead == value) 74 return; 75 onRead = value; 76 } 77 } 78 /// 79 /// 項的個數(shù) 80 /// 81 public int ItemCount 82 { 83 get { return itemCount; } 84 set 85 { 86 if(itemCount == value) 87 return; 88 itemCount=value; 89 } 90 } 91 /// 92 /// 客戶端指定的數(shù)據(jù)變化率 93 /// 94 public int RequestedUpdateRate 95 { 96 get 97 { 98 return requestedUpdateRate; 99 }100 set101 {102 if (requestedUpdateRate == value)103 return;104 requestedUpdateRate = value;105 }106 }107 108 /// 109 /// OPC組名稱110 /// 111 public string GroupName112 {113 get114 {115 return groupName;116 }117 set118 {119 if (groupName == value)120 return;121 groupName = value;122 }123 }124 125 /// 126 /// 客戶端程序為組對象提供的句柄127 /// 128 public int ClientGroupHandle129 {130 get131 {132 return clientGroupHandle;133 }134 set135 {136 if (clientGroupHandle == value)137 return;138 clientGroupHandle = value;139 }140 }141 142 /// 143 /// 指向Long類型的指針144 /// 145 public GCHandle TimeBias146 {147 get148 {149 return timeBias;150 }151 set152 {153 if (timeBias == value)154 return;155 timeBias = value;156 }157 }158 159 /// 160 /// 一個項對象的值變化的百分比,可能引發(fā)客戶端程序的訂閱回調(diào)。161 /// 此參數(shù)只應(yīng)用于組對象中有模擬dwEUType(工程單位)類型的項對象。指針為NULL表示0.0162 /// 163 public GCHandle PercendDeadBand164 {165 get166 {167 return percendDeadBand;168 }169 set170 {171 if (percendDeadBand == value)172 return;173 percendDeadBand = value;174 }175 }176 177 /// 178 /// 當(dāng)用于組對象上的操作的返回值為文本類型時,服務(wù)器使用的語言179 /// 180 public int LCID181 {182 get183 {184 return lcid;185 }186 set187 {188 if (lcid == value)189 return;190 lcid = value;191 }192 }193 194 /// 195 /// OPC項數(shù)組196 /// 197 public OpcDaCustomItem[] OpcDataCustomItems198 {199 get200 {201 return opcDataCustomItems;202 }203 set204 {205 if (opcDataCustomItems != null && opcDataCustomItems == value)206 return;207 opcDataCustomItems = value;208 }209 }210 }211 }  OpcItem的封裝:  1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Runtime.InteropServices; 6 using OpcRcw.Da; 7 8 namespace Opc.Net 9 { 10 /// 11 /// 自定義接口Opc項 12 /// 13 public class OpcDaCustomItem 14 { 15 private string name; 16 private string accessPath=''; 17 private string itemID; 18 private int isActive = 1; 19 private int clientHandle = 0; 20 private int blobSize = 0; 21 private IntPtr blob = IntPtr.Zero; 22 private short requestedDataType = 0; 23 private object itemValue; 24 private int serverHandle; 25 26 /// 27 /// 項名稱 28 /// 29 public string Name 30 { 31 get 32 { 33 return name; 34 } 35 set 36 { 37 if (name == value) 38 return; 39 name = value; 40 } 41 } 42 /// 43 /// 項對象的訪問路徑 44 /// 45 public string AccessPath 46 { 47 get 48 { 49 return accessPath; 50 } 51 set 52 { 53 if (accessPath == value) 54 return; 55 accessPath = value; 56 } 57 } 58 59 /// 60 /// 項對象的ItemIDea,唯一標識該數(shù)據(jù)項 61 /// 62 public string ItemID 63 { 64 get 65 { 66 return itemID; 67 } 68 set 69 { 70 if (itemID == value) 71 return; 72 itemID = value; 73 } 74 } 75 76 /// 77 /// 項對象的激活狀態(tài) 78 /// 1為激活,0為未激活,默認激活 79 /// 80 public int IsActive 81 { 82 get 83 { 84 return isActive; 85 } 86 set 87 { 88 if (isActive == value) 89 return; 90 isActive = value; 91 } 92 } 93 94 /// 95 /// 項對象的客戶端句柄 96 /// 97 public int ClientHandle 98 { 99 get100 {101 return clientHandle;102 }103 set104 {105 if (clientHandle == value)106 return;107 clientHandle = value;108 }109 }110 public int BlobSize111 {112 get113 {114 return blobSize;115 }116 set117 {118 if (blobSize == value)119 return;120 blobSize = value;121 }122 }123 public IntPtr Blob124 {125 get126 {127 return blob;128 }129 set130 {131 if (blob == value)132 return;133 blob = value;134 }135 }136 137 /// 138 /// OPC項的數(shù)據(jù)類型139 /// VbBoolean:11,VbByte:17,VbDecimal:14,VbDouble:5,Vbinteger:2,VbLong:3,VbSingle:4,VbString:8140 /// 141 public short RequestedDataType142 {143 get144 {145 return requestedDataType;146 }147 set148 {149 if (requestedDataType == value)150 return;151 requestedDataType = value;152 }153 }154 155 /// 156 /// OPC項的值157 /// 158 public object Value159 {160 get161 {162 return itemValue;163 }164 set165 {166 if (itemValue == value)167 return;168 itemValue = value;169 }170 }171 172 /// 173 /// OPC項的服務(wù)器句柄174 /// 175 public int ServerHandle176 {177 get178 {179 return serverHandle;180 }181 set182 {183 if (serverHandle == value)184 return;185 serverHandle = value;186 }187 }188 }189 }  項的客戶端句柄和服務(wù)器句柄實際是一樣的,項的數(shù)據(jù)類型用short表示,在下面的配置文件中體現(xiàn)出來了。 以下是我設(shè)計的配置文件:  1 <>xml version='1.0' encoding='utf-8'?> 2 System> 3 OpcServer ServerName='OPC.SimaticNET' IPAddress='10.102.102.118'> 4 5 ShearerInfo GroupName='ShearerInfoGroup' ClientHandle='1' UpdateRate='100'> 6 7 Item ItemID='S7:[S7 connection_2]DB201,X20.2' ClientHandle='1' RequestedDataType='11'>Item> 8 9 Item ItemID='S7:[S7 connection_2]DB201,X20.1' ClientHandle='2' RequestedDataType='11'>Item>10 11 Item ItemID='S7:[S7 connection_2]DB201,REAL40' ClientHandle='3' RequestedDataType='5'>Item>12 13 Item ItemID='S7:[S7 connection_2]DB201,REAL44' ClientHandle='4' RequestedDataType='5'>Item>14 15 Item ItemID='S7:[S7 connection_2]DB201,REAL48' ClientHandle='5' RequestedDataType='5'>Item>16 17 Item ItemID='S7:[S7 connection_2]DB201,REAL52' ClientHandle='6' RequestedDataType='5'>Item>18 19 Item ItemID='S7:[S7 connection_2]DB201,INT6' ClientHandle='7' RequestedDataType='2'>Item>20 21 Item ItemID='S7:[S7 connection_2]DB201,INT8' ClientHandle='8' RequestedDataType='2'>Item>22 23 Item ItemID='S7:[S7 connection_2]DB201,INT2' ClientHandle='9' RequestedDataType='2'>Item>24 25 Item ItemID='S7:[S7 connection_2]DB201,INT4' ClientHandle='10' RequestedDataType='2'>Item>26 27 Item ItemID='S7:[S7 connection_2]DB201,X20.6' ClientHandle='11' RequestedDataType='11'>Item>28 29 Item ItemID='S7:[S7 connection_2]DB201,X20.5' ClientHandle='12' RequestedDataType='11'>Item>30 31 Item ItemID='S7:[S7 connection_2]DB201,INT10' ClientHandle='13' RequestedDataType='2'>Item>32 33 Item ItemID='S7:[S7 connection_2]DB201,INT12' ClientHandle='14' RequestedDataType='2'>Item>34 35 Item ItemID='S7:[S7 connection_2]DB201,INT14' ClientHandle='15' RequestedDataType='2'>Item>36 37 Item ItemID='S7:[S7 connection_2]DB201,INT34' ClientHandle='16' RequestedDataType='2'>Item>38 ShearerInfo>39 OpcServer>40 System>  上述配置文件中,OpcServer節(jié)點對應(yīng)的OpcServer對象,定義了ServerName和IPAddress屬性,用來連接OPCServer。 ShearerInfo節(jié)點則對應(yīng)一個OpcGroup,在OpcServer下定義多個OPCGrupo節(jié)點,OPCGroup節(jié)點需要指定組的客戶端句柄和刷新頻率。上文說到OPC的讀寫操作都是以組進行的,我們需要根據(jù)客戶端句柄來判斷是哪一個組,如果我們采用的事訂閱模式讀取數(shù)據(jù),則還需要刷新頻率,OpcServer對訂閱模式的實現(xiàn)不太清楚,實際使用的過程發(fā)現(xiàn),并沒有按照刷新頻率來,所以我就采用了直接讀的方式來保證數(shù)據(jù)的實時性。 Item的ItemID是一個地址,由于我使用的是西門子的產(chǎn)品,所以格式是:S7:[S7連接名稱]地址,我們只需要更改S7連接的名稱和地址就好了。如果你使用的事其他類型的PLC,請參照他們的地址格式。 有了配置文件如何操作呢?下面我定義了一個實現(xiàn)類:    1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.IO; 5 using System.Runtime.InteropServices; 6 using System.Xml.Linq; 7 8 namespace Opc.Net 9 { 10 public class OpcManager 11 { 12 /// 13 /// Opc異步接口類 14 /// 15 OpcDaCustomAsync _opcDaCustomAsync; 16 /// 17 /// 異步讀取數(shù)據(jù)完成事件 18 /// 19 public event EventHandler OnReadCompleted; 20 /// 21 /// Opc組列表 22 /// 23 List _opcGroups; 24 /// 25 /// OPC服務(wù)器名稱 26 /// 27 string _strRemoteServerName; 28 /// 29 /// OPC服務(wù)器IP地址 30 /// 31 string _strRemoteServerIpAddress; 32 33 /// 34 /// 構(gòu)造函數(shù) 35 /// 36 /// 配置文件路徑 37 public OpcManager(string strConfigFilePath) 38 { 39 LoadOpcGroupConfig(strConfigFilePath); 40 } 41 /// 42 /// 加載Opc組配置 43 /// 44 /// 配置文件路徑 45 public void LoadOpcGroupConfig(string strConfigFilePath) 46 { 47 try 48 { 49 if (!File.Exists(strConfigFilePath)) return; 50 XDocument xDoc = XDocument.Load(strConfigFilePath); 51 XElement xElement = xDoc.Element('System').Element('OpcServer'); 52 _strRemoteServerName = xElement.Attribute('ServerName').Value; 53 _strRemoteServerIpAddress = xElement.Attribute('IPAddress').Value; 54 _opcGroups = new List(); 55 foreach (XElement xElementItem in xElement.Elements()) 56 { 57 var opcDaCustomGroupService = new OpcDaCustomGroup 58 { 59 GroupName = xElementItem.Attribute('GroupName').Value, 60 ClientGroupHandle = Convert.ToInt32(xElementItem.Attribute('ClientHandle').Value), 61 RequestedUpdateRate = Convert.ToInt32(xElementItem.Attribute('UpdateRate').Value), 62 OpcDataCustomItems = LoadOpcItemConfig(xElementItem) 63 }; 64 _opcGroups.Add(opcDaCustomGroupService); 65 } 66 _opcDaCustomAsync = new OpcDaCustomAsync(_opcGroups, _strRemoteServerName, _strRemoteServerIpAddress); 67 _opcDaCustomAsync.OnReadCompleted += ReadCompleted; 68 } 69 catch(COMException ex) 70 { 71 throw ex; 72 } 73 } 74 /// 75 /// 連接Opc服務(wù)器 76 /// 77 /// 78 public bool Connect() 79 { 80 return _opcDaCustomAsync.Connect(); 81 } 82 /// 83 /// 連接Opc服務(wù)器 84 /// 85 /// 86 public bool Connect(string remoteOpcServerName,string remoteOpcServerIpAddress) 87 { 88 return _opcDaCustomAsync.Connect(remoteOpcServerName, remoteOpcServerIpAddress); 89 } 90 /// 91 /// 加載Opc項配置 92 /// 93 /// Opc組Xml節(jié)點 94 /// 95 public OpcDaCustomItem[] LoadOpcItemConfig(XElement xElement) 96 { 97 int itemCount = xElement.Elements().Count(); 98 var opcDaCustomItems = new OpcDaCustomItem[itemCount]; 99 int i = 0;100 foreach (var xElementItem in xElement.Elements())101 {102 var opcDaCustomItemService = new OpcDaCustomItem103 {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 Dictionaryint, object>117 {118 {itemClientHandle, value}119 };120 try121 {122 int[] pErrors;123 Write(itemDictionary, clientHandle, out pErrors);124 returnValue = (pErrors[0] == 0);125 }126 catch (COMException ex)127 {128 throw ex;129 }130 return returnValue;131 }132 public void Write(Dictionaryint, 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 (KeyValuePairint, object> itemId in itemDictionary)141 {142 foreach (var item in group.OpcDataCustomItems)143 {144 if (item.ClientHandle == itemId.Key)145 {146 values[index] = itemId.Value;147 serverHandle[index] = item.ServerHandle;148 index++;149 }150 }151 }152 try153 {154 _opcDaCustomAsync.Write(values, serverHandle, out pErrors, group);155 }156 catch (COMException ex)157 {158 throw ex;159 }160 }161 /// 162 /// 寫單個數(shù)據(jù)163 /// 164 /// 值165 /// 組ID166 /// 項ID167 public void Write(int value, int groupHandle, int clientHandle)168 {169 OpcDaCustomGroup group = GetOpcGroup(groupHandle);170 if (group != null)171 {172 int[] pErrors;173 var serverHanlde = new int[1];174 serverHanlde[0] = group.OpcDataCustomItems.First(c => c.ClientHandle == clientHandle).ServerHandle;175 var values = new object[1];176 values[0] = value;177 178 _opcDaCustomAsync.Write(values, serverHanlde, out pErrors, group);179 180 }181 }182 /// 183 /// 異步讀取數(shù)據(jù)完成事件184 /// 185 /// 186 /// 187 public void ReadCompleted(object sender, OpcDaCustomAsyncEventArgs e)188 {189 if (OnReadCompleted != null)190 {191 OnReadCompleted(this, e);192 }193 }194 /// 195 /// 異步讀取控制模式數(shù)據(jù)196 /// 197 public void Read()198 {199 if (_opcDaCustomAsync != null)200 {201 _opcDaCustomAsync.Read();202 }203 204 }205 /// 206 /// 根據(jù)OPC句柄獲取OPC組對象207 /// 208 /// OPC組對象209 /// 210 public OpcDaCustomGroup GetOpcGroup(int groupHandle)211 {212 return _opcGroups.First(e => e.ClientGroupHandle == groupHandle);213 }214 }215 } View Code這個類可以根據(jù)自己設(shè)計的配置文件進行相應(yīng)的實現(xiàn)。  1 private OpcManager opcManager; 2 private System.Timers.Timer opcTimer; 3 private int[] pErrors; 4 private Dictionary int, object> items; 5 /// 6 /// 寫入采煤機位置數(shù)據(jù) 7 /// 8 /// 9 /// 10 private void button1_Click( object sender, EventArgs e)11 {12 items = new Dictionary int, object> ();13 items.Add( 2, textBox2.Text);14 opcManager.Write(items, 1, pErrors);15 }16 17 private void FrmMain_Load( object sender, EventArgs e)18 {19 opcManager = new OpcManager(AppDomain.CurrentDomain.BaseDirectory+ '\\Opc.config.xml');20 opcManager.OnReadCompleted += new EventHandler (opcManager_OnReadCompleted);21 22 opcTimer = new System.Timers.Timer()23 {24 Interval = 100,25 AutoReset = true,26 Enabled = true27 };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 }  以上實現(xiàn)了數(shù)據(jù)的讀取和寫入。 源碼戳這里:http://pan.baidu.com/s/1ntp1JAx
|