折騰了一段時間研究OPC,理清了下位機、OPCServer 和OPCClient的關系和通信模型,終於能夠來寫一篇相關的博客了。
我們使用西門子的 S7 200 SMART作為下位機端,通過3G路由器從vpn與公司服務器通信,服務器運行配置好的PC Access SMART 作為OPC Server, 完成對下位機內存地址的定義后,使用自動化接口開發中間件負責將OPC Server得到的PLC數據存放至SQL Server
中間件和數據庫的設計思路是:
數據庫按真實設備類型分別建表用作存儲,數據庫建有數據字典表用作配置功能,包括配置內存地址和opc服務等。
中間件作用是調用OPCAutomation 類訪問OPCServer端,並進行可控制讀取間隔的OPC數據讀取、存儲工作。
由於第一次接觸這類開發,在設計功能時花費了很多精力,從完全不懂到基本理解這種通信模式和原理,也繞了不少遠路。
本文主要介紹OPCAutomation類的使用。
簡單說下流程就是:
1. 創建OPC Server的連接
2. 創建OPC組對象並初始化設置
3. 獲取組的OPCItems對象,為讀取數據作准備
4. opcItem的操作。
5. 退出程序的資源釋放
創建連接很簡單,需要指定OPCServer所在的服務器(內網可以指定ip或者計算機名),指定OPC服務的名稱(同一服務器可能運行多個OPC服務以適配同的下位機)

private bool ConnectRemoteServer(string remoteServerIP, string remoteServerName) { try { this.opcServer.Connect(remoteServerName, remoteServerIP); string status; if (opcServer.ServerState == (int)OPCServerState.OPCRunning) { status = "已連接到-" + opcServer.ServerName + " "; } else { //這里你可以根據返回的狀態來自定義顯示信息,請查看自動化接口API文檔 status = "狀態:" + opcServer.ServerState.ToString() + " "; } Console.WriteLine(status); } catch (Exception err) { Console.WriteLine("連接遠程服務器出現錯誤:" + err.Message, "提示信息"); return false; } return true; }
其中 this.opcServer.Connect(remoteServerName, remoteServerIP);即OPCAutomation類提供的連接方法。
需要注意的是,在實際配置時,需要完全對OPC 服務端所在服務器上配置防火牆出入站規則后,OPC服務才能夠被其他服務器上的中間件訪問到。故我們選擇最簡單的方式,在本機運行中間件。
創建組相當於讀取到OPC上特定的項目,而具體的數據值是在每個項目下根據開發人員的定義而確定。
opcGroups = opcServer.OPCGroups; opcGroup = opcGroups.Add("OPCDOTNETGROUP"); SetGroupProperty(); opcGroup.DataChange += new DIOPCGroupEvent_DataChangeEventHandler(opcGroup_DataChange); //opcGroup.AsyncWriteComplete += new DIOPCGroupEvent_AsyncWriteCompleteEventHandler(KepGroup_AsyncWriteComplete); opcItems = opcGroup.OPCItems;
注釋的代碼是綁定寫操作的事件。
這段代碼是為OPCGroup對象進行初始化。 比較關鍵的是兩句綁定事件的,第一個綁定的是每當OPC數據有變化時觸發的事件。
數據變化的時間是由以下代碼中UpdateRate控制。

private void SetGroupProperty() { opcServer.OPCGroups.DefaultGroupIsActive = true; opcServer.OPCGroups.DefaultGroupDeadband = 0; opcGroup.UpdateRate = this.updateRate; opcGroup.IsActive = true; opcGroup.IsSubscribed = true; }
關於逐項配置的具體說明,建議參考OPCAutomation的api,如果有看到翻譯比較合適的后續會補充上來。
之后是為全局變量里的OPCGroup對象添加Items,在OPC中,每個opcItem會被分配一個客戶端句柄值,同時會被配置上服務端句柄。幾乎所有有關獲取指定OpcItem對象的方法均要求指定客戶端句柄值。
void AddAllOpcItem() { opcBrowser.ShowBranches(); opcBrowser.ShowLeafs(true); int count = this.opcBrowser.Count; if (this.opcItemsArray == null) { opcItemsArray = new List<string>(); } foreach (var item in opcBrowser) { opcItemsArray.Add(item.ToString()); } AddOpcItems(); } //逐項綁定句柄值 void AddOpcItems() { foreach (var item in this.opcItemsArray) { itmHandleClient = 1234; opcItem = opcItems.AddItem(item, itmHandleClient); itmHandleServer = opcItem.ServerHandle; } }
這段代碼是為了把獲取到的全部OPCItem添加到所創建的OPCGroup對象的opcItem集合中,以便我們所綁定的DataChange事件能夠被觸發並且正確對應到每個地址值上。
基本上到此,只要在所綁定的DataChange的實現代碼中完成具體的數據讀取業務,中間件的核心部分已經完成。
稍微提一下,OPC的數據項主要包含:名稱、條目id、地址、數據類型、數據值、工程單位上下限、時間戳和質量幾項,在實際業務中我們關注條目ID、數據類型、值、時間戳和質量,對於讀取的opc數據值而言,需要注意的是得到值是Dynamic類型的,需要正確進行判斷和轉換。質量返回值為0時基本可以認為下位機到服務端的數據鏈斷了,或者下位機對應項目沒有收到數據,據此可進行日志和提示的業務操作。
在OPC的數據類型中,REAL對應float, WORD對應16位整型,BOOL可以用bit或者Bool進行轉換,用bit和sql server的數據項可以比較方便對接。
第一次做這類開發,謬誤難免,希望多多指點。
感謝閱讀。