天天看點

上位機與PLC通訊及OPC資料采集實踐一

上位機與PLC通訊及OPC資料采集實踐一

背景

         由于項目的需要,上位機(工控機)需要與PLC通訊(AB的PLC)通過PLC控制相關的裝置;另一部分需要做一個資料采集程式從一個OPC伺服器(此伺服器作為多個PLC資料的服務端用的是AB的RsLinx)采集一部分PLC的資料供另一個應用程式用。

         由于之前沒有接觸過PLC,也沒有聽說過什麼OPC,是以從網上找些資料,由于做的時候實際的環境可以調試,很多東西在寫的時候都沒啥底,最後在現場調試的時候有些東西才搞清楚,整個過程還是費了些周折,有些東西雖然還是沒有完全弄懂,但基于這個項目的基本功能算是完成了。是以記錄下來,以便以來有可能用得着,也給初次接觸這塊的同學提供些參考。

上位機與PLC的通訊

上位機基于C#寫的,PLC是AB的,C#程式通過序列槽(RS232-485轉換器)與PLC通訊,協定用的是标準modbus,程式發指令給PLC,PLC來控制電磁閥,繼電器等工作。由于PLC本身沒有采集資料,上位機還要把采集到的資料寫到PLC,資料原本是float的,但傳給PLC時轉成small int(小于65536)的整數,PLC内部再作除法來還原float資料.

這部分基于序列槽通讀,modbus協定倒時沒費啥事。序列槽通訊可以用序列槽調試助手測試;

至于寫資料有沒有寫成功可以用 ModScan32來看,連上對應的序列槽,設定好波特率,從機位址就可以看到預設100個寄存器的值。

PLC還有一個I/O口是程式設計用的,連接配接後用rslogix可以檢視實時的狀态,不過不大會用,看到PLC程式設計的工程師是這麼用的。

資料采集程式與OPC伺服器的通訊與資料采集

資料采集程式與OPC伺服器在同一區域網路,但不同網段,可以Ping通,但在程式裡就是連不上OPC伺服器,提示RPC伺服器不可用。在網上找資料說是要配置服務端及用戶端的DCOM,于是照網上的方式把用戶端的機器設定DCOM,但服務端控制不了,對方的工程師說已經配置好了,但死活還是連不上,提示還是一樣。(是以遠端通路的方式還是沒有走通)

後來經過協商把采集程式安裝到OPC伺服器所在的機器上,在同一網段内果真可以連上,資料采集也沒有問題,估計開始還是網絡及權限方面的問題;采集程式也是根據網上找的改的,後面附上源碼。資料采集到後,通過socket發到另一台機器。

采集程式源碼如下:(C# vs2010)

引用 OpcNetApi.dll

OpcNetApi.Com.dll

這兩個dll在安裝Rslinx後安裝目錄下可以找到 ***\Rockwell Software\RSLinx\下

-----------------code begin--------------
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using Opc;
using Opc.Da;
using OpcCom;
 
namespace OPC
{
    /// <summary>
    /// opc伺服器資料采集類
    /// </summary>
    public class OPCSampling
    {
        private Opc.Da.Server m_server = null;//定義資料存取伺服器
        private Opc.Da.Subscription subscription1 = null;//定義組對象(訂閱者)
        private Opc.Da.SubscriptionState state1 = null;//定義組(訂閱者)狀态,相當于OPC規範中組的參數
        private Opc.IDiscovery m_discovery = new OpcCom.ServerEnumerator();//定義枚舉基于COM伺服器的接口,用來搜尋所有的此類伺服器。
        
        /// <summary>
        /// 類的啟動函數,供外部調用
        /// </summary>
        public void Work(string strHost,string strOPCServerName)
        {
            try
            {
                //查詢伺服器
                Opc.Server[] servers = m_discovery.GetAvailableServers(Specification.COM_DA_20, strHost, null);
 
                //daver表示資料存取規範版本,Specification.COMDA_20等于2.0版本。
                //host為計算機名,null表示不需要任何網絡安全認證。
                if (servers != null)
                {
                    //連上後在這裡看下OPC服務的名稱,記下來在系統配置裡更改opc伺服器服務名,重新開機程式再運作
                    SharedLibrary.Gloary.OPCServerList = "";
                    foreach (Opc.Da.Server server in servers)
                    {
                        SharedLibrary.Gloary.OPCServerList += server.Name + " \r\n";
                        //server即為需要連接配接的OPC資料存取伺服器。
                        if (String.Compare(server.Name, strOPCServerName, true) == 0)//為true忽略大小寫
                        {
                            m_server = server;//建立連接配接。
                            break;
 
                        }
                    }
                    if (SharedLibrary.Gloary.OPCServerList.Length < 1)
                    {
                        SharedLibrary.Gloary.OPCServerList = "未連接配接上OPC伺服器或沒有找到相應的OPC服務";
                    }
                }
 
                //連接配接伺服器
                if (m_server != null)//非空連接配接伺服器
                {
                    m_server.Connect();
                }
                else
                {
                    return;
                }
 
                #region  組1
                //設定組1狀态
                state1 = new Opc.Da.SubscriptionState();//組(訂閱者)狀态,相當于OPC規範中組的參數
                state1.Name = "OPCDataSampling1";//組名
                state1.ServerHandle = null;//伺服器給該組配置設定的句柄。
                state1.ClientHandle = Guid.NewGuid().ToString();//用戶端給該組配置設定的句柄。
                state1.Active = true;//激活該組。
                state1.UpdateRate = 30000;//重新整理頻率為1秒。
                state1.Deadband = 0;// 死區值,設為0時,伺服器端該組内任何資料變化都通知組。
                state1.Locale = null;//不設定地區值。
 
                //添加組
                subscription1 = (Opc.Da.Subscription)m_server.CreateSubscription(state1);//建立組1
                //定義Item清單
                //對應類型為:{Byte,Byte,Char,Short,String,Word,Boolean}
                string[] itemName = { "[27]F8:8", "[27]F8:9", "[21]F8:1", "[21]F8:2", "[24]N9:0", "[21]F8:4", "[25]F8:1", "[25]F8:0", "[25]F8:5" };
 
                //定義item清單
                Item[] items = new Item[9];//定義資料項,即item
                int i;
                for (i = 0; i < items.Length; i++)//item初始化指派
                {
                    items[i] = new Item();//建立一個項Item對象。
                    items[i].ClientHandle = Guid.NewGuid().ToString();//用戶端給該資料項配置設定的句柄。
                    items[i].ItemPath = null; //該資料項在伺服器中的路徑。
                    items[i].ItemName = itemName[i]; //該資料項在伺服器中的名字。
                }
 
                //添加Item
                subscription1.AddItems(items);
 
                //注冊回調事件
                subscription1.DataChanged += new Opc.Da.DataChangedEventHandler(this.OnDataChange1);
 
                以下測試同步讀
                以下讀整個組
                //ItemValueResult[] values = subscription1.Read(subscription1.Items);
                
                以下周遊讀到的全部值
                //foreach (ItemValueResult value in values)
                //{
                //    SharedLibrary.Gloary.OPCServerList += "ItemName=" + value.ItemName + "\r\n";
                //    SharedLibrary.Gloary.OPCServerList += "ItemValue=" + value.Value.ToString() + "\r\n";
                //    SharedLibrary.LogMs.Debug("ItemName=" + value.ItemName);
                //    SharedLibrary.LogMs.Debug("ItemValue=" + value.Value);
                //}
                #endregion
 
                取消回調事件
                //subscription.DataChanged -= new Opc.Da.DataChangedEventHandler(this.OnDataChange);
                移除組内item
                //subscription.RemoveItems(subscription.Items);
                結束:釋放各資源
                //m_server.CancelSubscription(subscription);//m_server前文已說明,通知伺服器要求删除組。        
                //subscription.Dispose();//強制.NET資源資源回收筒回收該subscription的所有資源。         
                //m_server.Disconnect();//斷開伺服器連接配接
            }
            catch (Exception ex1)
            {
                SharedLibrary.Gloary.OPCServerList += " 異常:"+ex1.Message.ToString();
            }
        }
 
        /// <summary>
        /// DataChange回調
        /// </summary>
        /// <param name="subscriptionHandle"></param>
        /// <param name="requestHandle"></param>
        /// <param name="values"></param>
        public void OnDataChange1(object subscriptionHandle, object requestHandle, ItemValueResult[] values)
        {
            //OnDataChangeDeal("1", subscriptionHandle, requestHandle, values);
            string siteID = "";
            MonitorCenterWS60.BLL.SiteBaseEMData bll = new MonitorCenterWS60.BLL.SiteBaseEMData();
            foreach (ItemValueResult item in values)
            {
                try
                {
 
                    MonitorCenterWS60.Model.SiteBaseEMData model = new MonitorCenterWS60.Model.SiteBaseEMData();
                    #region  針對itemname作資料處理
 
                    switch (item.ItemName)
                    {
                        case "[27]F8:8":
                            //f8:8        送水餘氯
                            siteID = "01618200210001";
                            model.ItemCode = "160";        //待指定
                            model.ItemValue = decimal.Parse(decimal.Parse(item.Value.ToString()).ToString("0.000"));
                            model.Memo = item.ItemName;
                            break;
                        case "[27]F8:9":
                            //f8:9        送水PH
                            siteID = "01618200210001";
                            model.ItemCode = "161";        //待指定
                            model.ItemValue = decimal.Parse(decimal.Parse(item.Value.ToString()).ToString("0.00"));
                            model.Memo = item.ItemName;
                            break;
                            //部分略
                    }
                    #endregion
 
                    #region 
 
                    if (SharedLibrary.Gloary.DataTransType == "1")
                    {
                        DataComm.DataSend.SendEMDataReal(model);
                    }
                    #endregion
                }
                catch
                { }
            }
        }
        /// <summary>
        /// ReadComplete回調
        /// </summary>
        /// <param name="requestHandle"></param>
        /// <param name="values"></param>
        public void OnReadComplete(object requestHandle, Opc.Da.ItemValueResult[] values)
        {
            /*Console.WriteLine("發生異步讀name:{0},value:{1}", values[0].ItemName, values[0].Value);
            if ((int)requestHandle == 1)
                Console.WriteLine("事件信号句柄為{0}", requestHandle);*/
        }
 
        /// <summary>
        /// WriteComplete回調
        /// </summary>
        /// <param name="requestHandle"></param>
        /// <param name="values"></param>
        public void OnWriteComplete(object requestHandle, Opc.IdentifiedResult[] values)
        {
            /*Console.WriteLine("發生異步寫name:{0},value:{1}", values[0].ItemName, values[0].GetType());
            if ((int)requestHandle == 2)
                Console.WriteLine("事件信号句柄為{0}", requestHandle);*/
        }
    }
}
-----------------code end-----------------      

需要注意的是服務名與item标簽名一定要對,這個也可以用opc client來測試下,Rslinx提供了這個工具

在RxLinx/tools 裡面有個 OPC Test Clent 打開後,添加組,再添加标簽,也可以從下方的裡面選擇現有的資料項,添加後可以檢視到實時資料;

疑問一:因為沒有用RsLinx配置過OPC伺服器,沒明白OPC伺服器上的資料項(server下面的group下的item)在哪配置,待搞清楚後再來更新,如果有知道的也請不吝賜教!!!謝謝!

繼續閱讀