OPC是Object Linking and Embedding(OLE)forProcess Control的縮寫,它是微軟公司的對象連接和嵌入技術在過程控制方面的應用。OPC以OLE/COM/DCOM技術爲基礎,採用客戶/服務器模式,爲工業自動化軟件面向對象的開發提供了統一的標準,這個標準定義了應用Microsoft操做系統在基於PC的客戶機之間交換自動化實時數據的方法,採用這項標準後,硬件開發商將取代軟件開發商爲本身的硬件產品開發統一的OPC接口程序,而軟件開發者可免除開發驅動程序的工做,充分發揮本身的特長,把更多的精力投入到其核心產品的開發上。編程
SimaticNet是西門子全集成自動化系統中的一個重要組成部分,它爲完善的工業自動化控制系統的通信提供部件和網絡,同時提供多個OPCServer,爲數據的外部訪問提供接口,本文主要以OPC.SimaticNET爲例說明。數組
90年代OPC基金會開發了一系列的通信接口好比 Data Access (DA), Alarm & Events (A&E), Historical Data Access (HDA) and Data eXchange (DX),統稱傳統OPC。今天主要使用的OPC DA通信方式,這個在1995年左右仍是很流行的方法,最近幾年OPC Foundation又開發了新的 OPC Unified Architecture (UA) 標準,更好的適應了工業4.0。關於傳統OPC和OPC UA的區別,後面會單獨來講。安全
許多OPC服務器,包括OPC.SimaticNet,是在COM平臺開發的,從而對於基於.NET框架下的C#語言,做爲客戶端程序語言訪問OPCServer,須要解決兩個平臺間無縫遷移的問題。OPC基金會對會員提供了OpcRcw動態連接庫,OPC NET COM 包裝器和OPC NET API,將OPC複雜的規範封狀成簡單易用的C#類 ,能夠比較容易地實現數據訪問。服務器
OPC主要包含兩種接口:CUSTOM標準接口和OLE自動化標準接口,自定義接口是服務商必須提供的,而自動化接口則是可選的。
自定義接口是一組COM接口,主要用於採用C++語言的應用程序開發;
自動化接口是一組OLE接口,主要用於採用VB,DELPHI,Excel等基於腳本編程語言的應用程序開發。本文是使用C#經過自動化接口來實現的,也是最簡單的方式。網絡
首先必須瞭解的是OPC服務器的對象模型:app
程序中涉及到的重要方法和屬性比較多,解釋下幾個容易搞混的:框架
OPCItem 對象的屬性ServerHandle,只讀屬性,服務器提供給Item的句柄,經過此句柄,Client能夠定位到此Item,來對此Item進行後續的操做,好比移動刪除;編程語言
OPCItem 對象的屬性ClientHandle,可讀可寫屬性,客戶端分配給Item的句柄,這個句柄能夠手動設置,也可由.NET隨機選取的,不須要咱們來設置,而且每次運行時,這
個句柄都不一樣,相似於TCP scoket通信中的Client端分配的端口號。Server端必須指定端口號,Client端隨機生成,每次都不同。測試
OPCGroup 對象的屬性的IsSubscribed,可讀可寫屬性,Group的IsSubscribed爲True,此Group才能開始接受服務器的數據屬性,此Group才能被訂閱。ui
OPCGroup 對象的事件DataChange (TransactionID As Long, NumItems As Long, ClientHandles() As Long,ItemValues() As Variant, Qualities() As Long, TimeStamps() As Date)須要注意的是NumItems參數是每次事件觸發時Group中實際發生數據變化的Item的數量,而不是整個Group裏的Items.
OPCGroup 對象的屬性UpdateRate,可讀可寫屬性,規定了數據刷新的週期,單位milliseconds.注意的是,不是設定多少ms,實際就是多少,好比給定53ms,OPC server會就近選擇50ms.有區間劃分的。
源程序以下:
[csharp] view plain copy
- using System;
- using System.Collections.Generic;
- using System.ComponentModel;
- using System.Data;
- using System.Drawing;
- using System.Text;
- using System.Windows.Forms;
-
- using System.Net;
- using OPCAutomation;
-
-
- namespace OpcClient
- {
- public partial class OpcClient : Form
- {
- public OpcClient()
- {
- InitializeComponent();
- }
-
- #region 私有變量
- private String strHostIP;
- private String strHostName;
- private Boolean opc_connected;
-
- private OPCServer LocalServer;
- private OPCGroups myGroups;
- private OPCGroup myGroup;
- private OPCGroup myGroup1;
- private OPCItems myItems;
- private OPCItems myItems1;
- private OPCItem myItem;
- int itmHandleClient = 0; /// 客戶端句柄
- int itmHandleServer = 0; /// 服務端句柄
- //**wfx
- private OPCItem [] myItemArray;
- private OPCItem[] myItemArray1;
-
- #endregion
-
- #region 私有方法
- /// <summary>
- /// 鏈接OPC服務器
- /// </summary>
- /// <param name="remoteServerIP">OPCServerIP</param>
- /// <param name="remoteServerName">OPCServer名稱</param>
- private bool ConnectRemoteServer(string remoteServerIP, string remoteServerName)
- {
- try
- {
- LocalServer.Connect(remoteServerName, remoteServerIP);
-
- if (LocalServer.ServerState == (int)OPCServerState.OPCRunning)
- {
- lblState.Text = "已鏈接到:" + "\r\n" + LocalServer.ServerName + "\r\n";
- //顯示服務器信息
- lblState.Text = lblState.Text + "開始時間:" + "\r\n" + LocalServer.StartTime.ToString() + "\r\n";
- lblState.Text = lblState.Text + "版本:" + LocalServer.MajorVersion.ToString() + "." + LocalServer.MinorVersion.ToString() + "." + LocalServer.BuildNumber.ToString();
- }
- else
- {
- //這裏你能夠根據返回的狀態來自定義顯示信息,請查看自動化接口API文檔
- lblState.Text = "狀態:" + LocalServer.ServerState.ToString() + "\r\n";
-
- }
- }
- catch (Exception err)
- {
- MessageBox.Show("鏈接遠程服務器出現錯誤:" + err.Message, "提示信息", MessageBoxButtons.OK, MessageBoxIcon.Warning);
- return false;
- }
- return true;
- }
-
- /// <summary>
- /// 每當項數據有變化時執行的事件
- /// </summary>
- /// <param name="TransactionID">處理ID</param>
- /// <param name="NumItems">項個數</param>
- /// <param name="ClientHandles">項客戶端句柄</param>
- /// <param name="ItemValues">TAG值</param>
- /// <param name="Qualities">品質</param>
- /// <param name="TimeStamps">時間戳</param>
- void myGroup_DataChange(int TransactionID, int NumItems, ref Array ClientHandles, ref Array ItemValues, ref Array Qualities, ref Array TimeStamps)
- {
- //爲了測試,因此加了控制檯的輸出,來查看事物ID號
- Console.WriteLine("********"+TransactionID.ToString()+NumItems.ToString()+"*********");//第二次進來後爲啥變成1了,以前都是4個
- //**wfx
- /*
- for (int i = 1; i <= NumItems; i++)
- {
- this.txtValue.Text = ItemValues.GetValue(i).ToString();
- this.txtQuality.Text = Qualities.GetValue(i).ToString();
- this.txtTime.Text = TimeStamps.GetValue(i).ToString();
- }
- */
- //**wfx
- TextBox[] tb = new TextBox[4];
- tb[0] = textBox1;
- tb[1] = textBox2;
- tb[2] = textBox3;
- tb[3] = textBox4;
- //ClientHandles.GetValue(i);
- for (int i = 1; i <= NumItems;i++ )
- {
- tb[(int)ClientHandles.GetValue(i)-1].Text = ((float)ItemValues.GetValue(i)).ToString("0.00");
- }
- }
-
- /// <summary>
- /// 寫入TAG值時執行的事件
- /// </summary>
- /// <param name="TransactionID"></param>
- /// <param name="NumItems"></param>
- /// <param name="ClientHandles"></param>
- /// <param name="Errors"></param>
- void myGroup_AsyncWriteComplete(int TransactionID, int NumItems, ref Array ClientHandles, ref Array Errors)
- {
- lblState.Text = "";
- for (int i = 1; i <= NumItems; i++)
- {
- lblState.Text += "TransactionID:" + TransactionID.ToString() + "\r\n" + "ClientHandle:" + ClientHandles.GetValue(i).ToString() + "\r\n" + "ErrorValue: " + Errors.GetValue(i).ToString() + "\r\n";
- }
- }
-
- /// <summary>
- /// 建立組
- /// </summary>
- private bool CreateGroup()
- {
- try
- {
- myGroups = LocalServer.OPCGroups;
- myGroup = myGroups.Add("OpcDotNetGroup");
- myGroup1 = myGroups.Add("OpcDotNetGroup1");
- SetDefaultGroupProperty();
- myItems = myGroup.OPCItems;
- myItems1 = myGroup1.OPCItems;
- //**wfx
- myItemArray =new OPCItem [16];
- myItemArray1 = new OPCItem[16];
- //**add items
- //DA格式 S7:[S7 connection_1]MX3.1
- //S7:[S7 connection_1]DB1,X3.0
- //OPC UA格式 S7:S7 connection_1.db5.68,r
- //S7:S7 connection_1.db1.20,x7
- myItemArray[0] = myItems.AddItem("S7:[S7 connection_1]db5,real68", 1);//Z軸
- myItemArray[1] = myItems.AddItem("S7:[S7 connection_1]db1,real50", 2);//刮刀
- myItemArray[2] = myItems.AddItem("S7:[S7 connection_1]db1,real96", 3);//送粉
- myItemArray[3] = myItems.AddItem("S7:[S7 connection_1]db1,real72", 4);//溫度
- //**wfx
- /*
- myItemArray1[4] = myItems1.AddItem("S7:[S7 connection_1]MX3.1", 4);//Z 正
- myItemArray1[5] = myItems1.AddItem("S7:[S7 connection_1]MX3.6", 5);//Z 負
- */
- myGroup.DataChange += new DIOPCGroupEvent_DataChangeEventHandler(myGroup_DataChange);
- myGroup.AsyncWriteComplete += new DIOPCGroupEvent_AsyncWriteCompleteEventHandler(myGroup_AsyncWriteComplete);
-
- }
- catch (Exception err)
- {
- MessageBox.Show("建立組出現錯誤:" + err.Message, "提示信息", MessageBoxButtons.OK, MessageBoxIcon.Warning);
- return false;
- }
- return true;
- }
-
- /// <summary>
- /// 設置缺省的組屬性
- /// </summary>
- private void SetDefaultGroupProperty()
- {
- LocalServer.OPCGroups.DefaultGroupIsActive = true ;
- LocalServer.OPCGroups.DefaultGroupDeadband = 0 ;
- myGroup.UpdateRate = 250 ;
- myGroup.IsActive = true ;
- myGroup.IsSubscribed = true ;
- myGroup1.UpdateRate = 250;
- myGroup1.IsActive = true;
- myGroup1.IsSubscribed = true;
- }
-
-
- #endregion
-
- #region 窗體事件
-
- //窗體載入時,查詢本機安裝的OPC Server 列表
- private void OpcClient_Load(object sender, EventArgs e)
- {
-
-
- //獲取本地計算機IP,計算機名稱
- IPHostEntry IPHost = Dns.GetHostEntry(Environment.MachineName);
- IPHost.HostName.ToString();
- if (IPHost.AddressList.Length > 0)
- {
- strHostIP = IPHost.AddressList[0].ToString();
- }
- else
- {
- return;
- }
- //經過IP來獲取計算機名稱,可用在局域網內
- IPHostEntry ipHostEntry = Dns.GetHostEntry(strHostIP);
- strHostName = ipHostEntry.HostName.ToString();
-
- //獲取本地計算機上的OPCServerName
- try
- {
- LocalServer = new OPCServer();
- object serverList = LocalServer.GetOPCServers(strHostName);
-
- foreach (string turn in (Array)serverList)
- {
- cmbServer.Items.Add(turn);
- }
-
- cmbServer.SelectedIndex = 0;
- btnConnect.Enabled = true;
- }
- catch (Exception err)
- {
- MessageBox.Show("枚舉本地OPC服務器出錯:" + err.Message, "提示信息", MessageBoxButtons.OK, MessageBoxIcon.Warning);
- }
-
- }
-
-
- //點擊"鏈接"按鈕, 動做
- private void btnConnect_Click(object sender, EventArgs e)
- {
- //鏈接服務器
- try
- {
- if (!ConnectRemoteServer("127.0.0.1", cmbServer.Text))
- {
- return;
- }
-
- opc_connected = true; //已鏈接標記
-
- OPCBrowser oPCBrowser = LocalServer.CreateBrowser();
- //展開分支
- oPCBrowser.ShowBranches();
- //展開葉子
- oPCBrowser.ShowLeafs(true);
- lstItems.Items.Clear(); //清空列表
- foreach (object turn in oPCBrowser)
- {
- lstItems.Items.Add(turn.ToString());
- }
-
- if (!CreateGroup())
- {
- return;
- }
- }
- catch (Exception err)
- {
- MessageBox.Show("初始化出錯:" + err.Message, "提示信息", MessageBoxButtons.OK, MessageBoxIcon.Warning);
- }
-
- }
-
-
-
-
- /// 選擇列表中的某個Item時處理的事情
- private void lstItems_SelectedIndexChanged(object sender, EventArgs e)
- {
- try
- {
- this.txtName.Text = lstItems.SelectedItem.ToString();
- if (itmHandleClient != 0)
- {
- this.txtValue.Text = "";
- this.txtQuality.Text = "";
- this.txtTime.Text = "";
-
- Array Errors;
- OPCItem bItem = myItems.GetOPCItem(itmHandleServer);
- //注:OPC中以1爲數組的基數
- int[] temp = new int[2] { 0, bItem.ServerHandle };
- Array serverHandle = (Array)temp;
- //移除上一次選擇的項
- myItems.Remove(myItems.Count, ref serverHandle, out Errors);
- }
- itmHandleClient = 1234;
- myItem = myItems.AddItem(lstItems.SelectedItem.ToString(), itmHandleClient);
- itmHandleServer = myItem.ServerHandle;
- Console.WriteLine("*****" + itmHandleServer+"*****");//ServerHandle是隨機分配,比如Socket的客戶端端口號也是隨機分配同樣,ClientHandle是用戶指定,
- }
- catch (Exception err)
- {
- //沒有任何權限的項,都是OPC服務器保留的系統項,此處可不作處理。
- itmHandleClient = 0;
- txtValue.Text = "Error ox";
- txtQuality.Text = "Error ox";
- txtTime.Text = "Error ox";
- MessageBox.Show("此項爲系統保留項:" + err.Message, "提示信息");
- }
- }
-
-
- //點擊"寫入"按鈕
- private void cmdWrite_Click(object sender, EventArgs e)
- {
- OPCItem bItem = myItems.GetOPCItem(itmHandleServer);
- int[] temp = new int[2] { 0, bItem.ServerHandle };
- Array serverHandles = (Array)temp;
- object[] valueTemp = new object[2] { "", txtNewValue.Text };
- Array values = (Array)valueTemp;
- Array Errors;
- int cancelID;
- myGroup.AsyncWrite(1, ref serverHandles, ref values, out Errors, 2009, out cancelID);
- //myItem.Write(txtNewValue.Text);//這句也能夠寫入,但並不觸發寫入事件
- GC.Collect();
- }
-
-
- //點擊"斷開" 按鈕
- private void btnDisConn_Click(object sender, EventArgs e)
- {
- if (!opc_connected)
- {
- return;
- }
-
- if (myGroup != null)
- {
- myGroup.DataChange -= new DIOPCGroupEvent_DataChangeEventHandler(myGroup_DataChange);
- }
-
- if (LocalServer != null)
- {
- LocalServer.Disconnect();
- //LocalServer = null;
-
- }
-
- opc_connected = false;
- lstItems.Items.Clear();
-
- //顯示信息
- lblState.Text = "已經從OPC服務器斷開." + "\r\n" ;
- //顯示服務器信息
- lblState.Text = lblState.Text + "斷開時間:" + "\r\n" + System.DateTime.Now.ToString() + "\r\n";
-
- }
-
-
-
-
- //關閉窗體時候,清理工做
- private void OpcClient_FormClosing(object sender, FormClosingEventArgs e)
- {
- btnDisConn_Click(new Object(),new EventArgs());
- }
-
- #endregion
-
- private void button1_Click(object sender, EventArgs e)
- {
- myItemArray1[6] = myItems1.AddItem("S7:[S7 connection_1]db1,x0.3", 6);//刮刀正
- myItemArray1[10] = myItems1.AddItem("S7:[S7 connection_1]db1,real41", 10);//刮刀速度
- myItemArray1[11] = myItems1.AddItem("S7:[S7 connection_1]db1,real33", 11);//刮刀位置
-
- int[] temp = new int[4] { 0, myItemArray1[6].ServerHandle, myItemArray1[10].ServerHandle, myItemArray1[11].ServerHandle };
- Array serverHandles = (Array)temp;
- object[] valueTemp = new object[4] { "", true, textBox6.Text, textBox7.Text };
- Array values = (Array)valueTemp;
- Array Errors;
- int cancelID;
- myGroup1.AsyncWrite(3, ref serverHandles, ref values, out Errors, 2009, out cancelID);
- //myItem.Write(txtNewValue.Text);//這句也能夠寫入,但並不觸發寫入事件
-
- }
-
- private void button2_Click(object sender, EventArgs e)
- {
- myItemArray1[7] = myItems1.AddItem("S7:[S7 connection_1]db1,x0.7", 7);//刮刀負
- myItemArray1[10] = myItems1.AddItem("S7:[S7 connection_1]db1,real41", 10);//刮刀速度
- myItemArray1[11] = myItems1.AddItem("S7:[S7 connection_1]db1,real33", 11);//刮刀位置
-
- int[] temp = new int[4] { 0, myItemArray1[7].ServerHandle, myItemArray1[10].ServerHandle, myItemArray1[11].ServerHandle };
- Array serverHandles = (Array)temp;
- object[] valueTemp = new object[4] { "", true, textBox6.Text, textBox7.Text };
- Array values = (Array)valueTemp;
- Array Errors;
- int cancelID;
- myGroup1.AsyncWrite(3, ref serverHandles, ref values, out Errors, 2009, out cancelID);
- //myItem.Write(txtNewValue.Text);//這句也能夠寫入,但並不觸發寫入事件
- }
-
- private void button3_Click(object sender, EventArgs e)
- {
- int[] temp = new int[4] { 0, myItemArray[8].ServerHandle, myItemArray[12].ServerHandle, myItemArray[13].ServerHandle };
- Array serverHandles = (Array)temp;
- object[] valueTemp = new object[4] { "", true, textBox8.Text, textBox9.Text };
- Array values = (Array)valueTemp;
- Array Errors;
- int cancelID;
- myGroup.AsyncWrite(3, ref serverHandles, ref values, out Errors, 2009, out cancelID);
- //myItem.Write(txtNewValue.Text);//這句也能夠寫入,但並不觸發寫入事件
- }
-
- private void button4_Click(object sender, EventArgs e)
- {
- int[] temp = new int[4] { 0, myItemArray[9].ServerHandle, myItemArray[12].ServerHandle, myItemArray[13].ServerHandle };
- Array serverHandles = (Array)temp;
- object[] valueTemp = new object[4] { "", true, textBox8.Text, textBox9.Text };
- Array values = (Array)valueTemp;
- Array Errors;
- int cancelID;
- myGroup.AsyncWrite(3, ref serverHandles, ref values, out Errors, 2009, out cancelID);
- //myItem.Write(txtNewValue.Text);//這句也能夠寫入,但並不觸發寫入事件
- }
-
- }
- }
最後,從總體上說下OPC DA的協議規範,OPC DA是在WINDOWS的COM/DOM技術上定義的接口定義,在TCP IP七層模型的最高層應用層,決定了它必須運行在WINDOWS平臺,不可以跨平臺,靈活性和安全性不如OPC UA,由於OPC DA的會話層和表示層用戶是有權利來使用的。對比OPC DA 和OPC UA的協議規範以下:更多不一樣點,後面會單獨再說。