C#.Net平台與OPC服務器通訊


最近,我們Ndolls工作室承接了山大某個自動化控制項目,主要做了一套工控信息化系統,其中有一個功能模塊是將系統管理的一部分數據參數發送至OPC服務器,由OPC服務器接收數據后執行相應工控操作。第一次接觸OPC的項目,有點頭大,與大家分享一點經驗,希望對大家有所幫助。

一、開發調試環境

1、系統環境:win7 64位

2、開發工具:Microsoft Visual Studio 2010(.Net4.0)

3、OPC組件:Interop.OPCAutomation.dll

下載地址:http://www.ndolls.net/doc/OPCAutomation.zip

4、OPC Server仿真軟件:ICONICS Simulator OPC Server 3.12

下載地址(需要登陸后下載):http://www.ndolls.net/doc/ICONICSSimulatorOPCServer3.12.zip

二、注意事項

1、Interop.OPCAutomation.dll需要在系統進行注冊,可以直接運行如下命令:

regsvr32 Interop.OPCAutomation.dll存放路徑

如因為win7系統權限問題無法注冊成功,請使用如下命令:

runas /user:administrator regsvr32 Interop.OPCAutomation.dll存放路徑

2、請將項目設置成X86模式,不然Interop.OPCAutomation.dll會調用失敗。

3、使用仿真軟件ICONICS Simulator OPC Server需要開啟Distributed Transaction Coordinator服務。

4、在調用Interop.OPCAutomation.dll過程中,如遇到返回錯誤碼,可參考以下地址進行分析解決:

https://www.cnblogs.com/heroius/p/7401026.html

5、Interop.OPCAutomation.dll中文文檔可從以下地址下載:

http://www.ndolls.net/doc/OPC_API.pdf

三、Demo界面以及代碼分析(下載地址:http://www.ndolls.net/doc/OPCManage.zip)

Demo主要實現了向OPC服務器指定的分組和標簽定向發送數據。

下圖為demo的winform界面

下圖為仿真軟件界面

下面貼上代碼,請注意首先需要添加引用using OPCAutomation

        #region 變量

        //OPCServer的IP
        private String strHostIP;
        //OPCServer的主機名
        private String strHostName;
        //是否和OPCServer建立連接
        private Boolean opc_connected;
        //建立的OPCServer對象
        private OPCServer myServer;
        //OPCServer節點瀏覽器
        private OPCBrowser myOPCBrowser;
        //分組集合
        private OPCGroups myGroups;
        //分組實例
        private OPCGroup myGroup;
        //分組的TAG節點
        private OPCItems myItems;
        //服務端句柄
        int itmHandleServer = 0;
        //要寫入的葉子節點
        private OPCItem[] myItemArray;

        #endregion

        #region 觸發事件

        /// <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)
        {
            for (int i = 1; i <= NumItems; i++)
            {
                txtValue.Text = ItemValues.GetValue(i).ToString();
            }

            //為方便測試,顯示到狀態欄輸出
            lblState.Text = "TransactionID:" + TransactionID.ToString() + "--" + "NumItems:" + NumItems.ToString();
        }

        /// <summary>
        /// 寫入TAG值時執行的事件
        /// </summary>
        /// <param name="TransactionID">處理ID</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)
        {
            //為方便測試,顯示到狀態欄輸出
            for (int i = 1; i <= NumItems; i++)
            {
                lblState.Text = "TransactionID:" + TransactionID.ToString() + "--" + "ClientHandle:" + ClientHandles.GetValue(i).ToString() + "--" + "ErrorValue: " + Errors.GetValue(i).ToString();
            }
        }

        #endregion

        #region 按鈕事件


        /// <summary>
        /// 連接服務器
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnConIP_Click(object sender, EventArgs e)
        {
            //根據輸入獲取OPC服務器IP地址
            strHostIP = txtIP.Text;

            //通過IP來獲取OPC服務器主機名
            IPHostEntry ipHostEntry = Dns.GetHostEntry(strHostIP);
            strHostName = ipHostEntry.HostName.ToString();

            try
            {
                //實例化OPC服務
                myServer = new OPCServer();

                //獲取OPCServer列表
                object serverList = myServer.GetOPCServers(strHostName);

                //將OPCServer展示到ComboBox
                foreach (string turn in (Array)serverList)
                {
                    cmbServer.Items.Add(turn);
                }
                cmbServer.SelectedIndex = 0;

                //開啟OPC連接按鈕
                btnOPC.Enabled = true;
            }
            catch (Exception err)
            {
                MessageBox.Show("枚舉OPC服務出錯:" + err.Message, "提示信息", MessageBoxButtons.OK, MessageBoxIcon.Warning);
            }
        }

        /// <summary>
        /// 連接OPC服務器
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnOPC_Click(object sender, EventArgs e)
        {
            try
            {
                //根據選擇的OPCServer進行連接
                myServer.Connect(cmbServer.Text, strHostIP);

                //根據連接成功與否輸出狀態信息
                if (myServer.ServerState == (int)OPCServerState.OPCRunning)
                {
                    lblState.Text = "已連接到:" + myServer.ServerName;
                    //顯示服務器信息
                    lblState.Text += "----開始時間:" + myServer.StartTime.ToString();
                    lblState.Text += "----版本:" + myServer.MajorVersion.ToString() + "." + myServer.MinorVersion.ToString() + "." + myServer.BuildNumber.ToString();
                }
                else
                {
                    lblState.Text = "狀態:" + myServer.ServerState.ToString();
                }

                //已連接標記
                opc_connected = true;

                //開啟獲取標簽按鈕
                btnGetGrps.Enabled = true;

                MessageBox.Show("鏈接OPC成功");
            }
            catch (Exception err)
            {
                MessageBox.Show("初始化出錯:" + err.Message, "提示信息", MessageBoxButtons.OK, MessageBoxIcon.Warning);
            }
        }

        /// <summary>
        /// 獲取OPC服務器所有分組和標簽
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnGetGrps_Click(object sender, EventArgs e)
        {
            //實例化Tag瀏覽器
            myOPCBrowser = myServer.CreateBrowser();

            //展開分組
            myOPCBrowser.ShowBranches();

            //展開標簽
            myOPCBrowser.ShowLeafs(true);

            //將所有分支和葉子節點顯示到ListBox
            lstItems.Items.Clear();
            foreach (object turn in myOPCBrowser)
            {
                lstItems.Items.Add(turn.ToString());
            }

            //開啟定位標簽按鈕
            btnSetItem.Enabled = true;
        }


        /// <summary>
        /// 根據列表中選中的當前標簽,定位到需要發送數據的標簽
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnSetItem_Click(object sender, EventArgs e)
        {
            try
            {
                /**
                 * 需要注意,不同的OPC服務器的標簽格式也是不同的
                 * 測試時,使用的是ICONICS Simulator OPC Server,標簽格式如:Textual.Memory
                 * 生產環境時,使用的是SimaticNet_V13Sp1,標簽格式如:S7:[S7_Connection_1]MReal120
                 * **/

                //根據ListBox選中的標簽,處理得到分組名稱
                string groupName = lstItems.Text;

                //實例化組
                myGroups = myServer.OPCGroups;
                myGroup = myGroups.Add(groupName);

                //設置缺省的組屬性
                myServer.OPCGroups.DefaultGroupIsActive = true;
                myServer.OPCGroups.DefaultGroupDeadband = 0;
                myGroup.UpdateRate = 250;
                myGroup.IsActive = true;
                myGroup.IsSubscribed = true;

                //定位需要發送數據的目標項
                myItems = myGroup.OPCItems;

                //實例化組內標簽
                myItemArray = new OPCItem[1];

                //填充項目組
                myItemArray[0] = myItems.AddItem(lstItems.Text, 1);

                //獲取服務端句柄
                itmHandleServer = myItemArray[0].ServerHandle;

                //監聽組內數據變化
                myGroup.DataChange += new DIOPCGroupEvent_DataChangeEventHandler(myGroup_DataChange);
                myGroup.AsyncWriteComplete += new DIOPCGroupEvent_AsyncWriteCompleteEventHandler(myGroup_AsyncWriteComplete);

                //開啟發送參數按鈕
                btnWrite.Enabled = true;
            }
            catch (Exception err)
            {
                MessageBox.Show("創建組出現錯誤:" + err.Message, "提示信息", MessageBoxButtons.OK, MessageBoxIcon.Warning);
            }
        }

        /// <summary>
        /// 寫入值
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnWrite_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] { "", txtMyValue.Text };
            Array values = (Array)valueTemp;
            Array Errors;
            int cancelID;

            //異步寫入到OPC服務器
            myGroup.AsyncWrite(1, ref serverHandles, ref values, out Errors, 2009, out cancelID);

            //回收資源
            GC.Collect();
        }


        /// <summary>
        /// 關閉窗體時,關閉鏈接
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void OPCManage_FormClosing(object sender, FormClosingEventArgs e)
        {
            if (!opc_connected)
            {
                return;
            }

            if (myGroup != null)
            {
                myGroup.DataChange -= new DIOPCGroupEvent_DataChangeEventHandler(myGroup_DataChange);
            }

            if (myServer != null)
            {
                myServer.Disconnect();
            }

            opc_connected = false;
        }

        #endregion

四、生產環境

真實的生產環境中,工控終端采用西門子數控系統(SINUMERIK 808),OPC服務器采用SimaticNet_V13Sp1。由工控人員預先在OPC服務的S7:[S7_Connection_1]M分組下,創建MReal120—MReal140共6個寄存節點,用於接收軟件寫入的工藝參數。本系統用戶根據需要從我們的軟件中檢索工藝參數,並定向寫入到OPC服務器的S7:[S7_Connection_1]M節點下,由數控人員控制數控系統從OPC服務器獲取到最新參數進行生產。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM