1. 程式人生 > >C#創建OPC Client來訪問OPC server

C#創建OPC Client來訪問OPC server

獲取 p地址 date connected handle 地址 只需要 datetime content

  最近一個項目,需要跟PLC通訊,所以測試使用了OPC server。現主要記錄使用C#編寫的Client例程,其它方面不作詳細描述。

  第一步,OPC Server使用的是KEPServer 5版本,網上很多資料。安裝完成後,它的配置頁面如下圖。配置中,我已配置了和Omron PLC連接的project,創建了訪問PLC的area地址的幾十個變量。具體配置根據不同PLC的信息對應配置就行了。技術分享圖片

  第二步,開始編寫C#程序。因為我的代碼是嵌套在現有的項目上的,所以創建了一個類來實現。大概的流程就是軟件開啟->創建與OPC Server通訊的Client線程。線程方法即為循環判斷通訊是否有掉線,若掉線則斷開重新連接。首先要在項目添加OPC的dll引用 Interop.OPCAutomation.dll

1、創建線程

          #region OPC通訊線程
            try
            {
                OPCClient opcClient = new OPCClient();
                Thread thrOpc = new Thread(opcClient.OPCClientOperate);
                thrOpc.IsBackground = true;
                thrOpc.Start();
            }
            catch { }
            #endregion    

2、創建類

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using OPCAutomation;
using Model;
using System.Threading;

namespace BLL
{
    public class OPCClient
    {
        #region 全局變量
        /// <summary>
        /// OPC對應PLC的位置
        /// </summary>
        public static Dictionary<string, OPCItemParameter> dtOpcToPlc = new Dictionary<string, OPCItemParameter>();
        /// <summary>
        /// opc服務器信息
        /// </summary>
        public static OPCInformation opcInformation = new OPCInformation();
        #endregion
        /// <summary>
        /// 初始化
        /// </summary>
        public OPCClient()
        { }
    }
}    

上述中的dtOpcToPlc為後臺配置,用於配置對應在OPC Server中想獲取的變量的名稱及對應的屬性,因為項目實施時可能有變動,對於信息的獲取只能用配置的形式了。

OPCInformation則是創建的與OPC通訊的信息實例了

兩個實例如下

/// <summary>
    /// opc參數信息
    /// </summary>
    public class OPCItemParameter
    {
        public OPCItemParameter()
        {
            this.ChangeTime = DateTime.Now;
        }
        /// <summary>
        /// 初始化
        /// </summary>
        /// <param name="pName">opc項名稱</param>
        /// <param name="plcName">PLC命名</param>
        /// <param name="handle">客戶端句柄</param>
        /// <param name="value">值</param>
        public OPCItemParameter(string pName, string plcName, int handle, int value)
        {
            this.ParameterName = pName;
            this.PLCName = plcName;
            this.ItemHandle = handle;
            this.Value = value;
            this.ChangeTime = DateTime.Now;
            this.IsWriteOk = false;
        }
        /// <summary>
        /// 客戶端參數句柄
        /// </summary>
        public int ItemHandle { get; set; }
        /// <summary>
        /// 對應PLC值的參數名稱(OPC server命名)
        /// </summary>
        public string ParameterName { get; set; }
        /// <summary>
        /// PLC位置名稱
        /// </summary>
        public string PLCName { get; set; }
        /// <summary>
        /// 參數值 
        /// </summary>
        public int Value { get; set; }
        /// <summary>
        /// 品質
        /// </summary>
        public string Qualities { get; set; }
        /// <summary>
        /// 時間戳
        /// </summary>
        public string TimeStamps { get; set; }
        /// <summary>
        /// 值發生變化的時間,用於後期任務優先級
        /// </summary>
        public DateTime ChangeTime { get; set; }
        /// <summary>
        /// 是否寫入成功
        /// </summary>
        public bool IsWriteOk { get; set; }
    }
 #region OPC服務器類信息
    /// <summary>
    /// OPC服務器的參數信息
    /// </summary>
    public class OPCInformation
    {
        public OPCInformation()
        {
            this.Ip = string.Empty;
            this.HostName = string.Empty;
            this.ConnectState = false;
            this.GroupsState = false;
            this.ConnectContents = "Opc Failed";
        }
        /// <summary>
        /// ip地址
        /// </summary>
        public string Ip { get; set; }
        /// <summary>
        /// 名稱
        /// </summary>
        public string HostName { get; set; }
        /// <summary>
        /// opc服務器名稱
        /// </summary>
        public string ServerName { get; set; }
        /// <summary>
        /// 服務器句柄
        /// </summary>
        public int itmHandleServer { get; set; }
        /// <summary>
        /// opc服務器對象
        /// </summary>
        public OPCServer KepServer { get; set; }
        /// <summary>
        /// opc組別集合對象
        /// </summary>
        public OPCGroups KepGroups { get; set; }
        /// <summary>
        /// opc組別對象
        /// </summary>
        public OPCGroup KepGroup { get; set; }
        /// <summary>
        /// opc項集合對象
        /// </summary>
        public OPCItems KepItems { get; set; }
        /// <summary>
        /// opc項對象
        /// </summary>
        public OPCItem KepItem { get; set; }

        /// <summary>
        /// 連接狀態
        /// </summary>
        public bool ConnectState { get; set; }
        /// <summary>
        /// 連接內容
        /// </summary>
        public string ConnectContents { get; set; }
        /// <summary>
        /// 創建群組是否成功
        /// </summary>
        public bool GroupsState { get; set; }
    }
    #endregion

3、下面為創建連接通訊及循環判斷是否掉線,這個主要是為了新創建連接及掉線是能迅速響應重連

 /// <summary>
        /// 對opc獲取的數據進行業務處理
        /// </summary>
        public void OPCClientOperate()
        {
            int lineoffCount = 0;//掉線判斷計數
            while (true)
            {
                try
                {
                    if (!opcInformation.ConnectState)
                    {//連接不成功,嘗試重新連接
                        if (GetLocalServer())
                        {//獲取OPC服務器信息成功
                            if (ConnectRemoteServer())
                            {//連接OPC成功 
                                opcInformation.ConnectState = true;
                                RecurBrowse(opcInformation.KepServer.CreateBrowser());
                            }
                        }
                        else
                        {
                            Thread.Sleep(3000);
                        }
                    }
                    else
                    {
                        if (!opcInformation.GroupsState)
                        {//創建組集合失敗,嘗試重新創建
                            opcInformation.GroupsState = CreateGroup();
                        }
                        else
                        {
                            //判斷狀態及時重連  
                        }
                    }
                }
                catch (Exception ex)
                {
                    opcInformation.ConnectState = false;
                    opcInformation.ConnectContents = "OPC disconnected";
                    opcInformation.GroupsState = false;
                    try
                    {
                        opcInformation.KepServer.Disconnect();
                    }
                    catch { }
                }
                //Thread.Sleep(100);
            }
        }

4、當連接上時,OPC的dll控件有數據變化響應事件創建調用就行了

 /// <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>
        private void KepGroup_DataChange(int TransactionID, int NumItems, ref Array ClientHandles, ref Array ItemValues, ref Array Qualities, ref Array TimeStamps)
        {
            for (int i = 1; i <= NumItems; i++)
            {
                try
                {
                    int index = int.Parse(ClientHandles.GetValue(i).ToString());
                    string key = dtOpcToPlc.FirstOrDefault(o => o.Value.ItemHandle == index).Key;
                    if (!string.IsNullOrEmpty(key))
                    {//需要判斷類型,是int還是boolean

                        int value = int.Parse(ItemValues.GetValue(i).ToString());
                        if (value != dtOpcToPlc[key].Value)
                        {
                            dtOpcToPlc[key].Value = value;
                            dtOpcToPlc[key].ChangeTime = DateTime.Now;
                        }
                        dtOpcToPlc[key].Qualities = Qualities.GetValue(i).ToString();
                        dtOpcToPlc[key].TimeStamps = TimeStamps.GetValue(i).ToString();

                    }
                }
                catch { }
            }
        }

5、向OPC Server的變量寫入數據

        /// <summary>
        /// 向OPC對應項寫入值
        /// </summary>
        /// <param name="value">需要寫入的值</param>
        /// <param name="OPCItemParameter">item地址</param>
        /// <returns></returns>
        public static bool WriteOpc(int value, OPCItemParameter opcItem)
        {
            try
            {
                string key = dtOpcToPlc.First(o => o.Value.ItemHandle == opcItem.ItemHandle).Key;
                dtOpcToPlc[key].IsWriteOk = false;
                OPCItem bItem = opcInformation.KepItems.Item(opcItem.ParameterName);
                opcInformation.itmHandleServer = bItem.ServerHandle;
                int[] temp = new int[2] { 0, bItem.ServerHandle };
                Array serverHandles = (Array)temp;
                object[] valueTemp = new object[2] { "", value.ToString() };
                Array values = (Array)valueTemp;
                Array Errors;
                int cancelID;
                opcInformation.KepGroup.AsyncWrite(1, ref serverHandles, ref values, out Errors, 2009, out cancelID);
                //KepItem.Write(txtWriteTagValue.Text);//這句也可以寫入,但並不觸發寫入事件
                GC.Collect();
                return true;
            }
            catch
            {
                return false;
            }
        }

很簡單,只需要調用對應的函數就可以了。

6、寫入成功響應

  當寫入成功後,對應的響應函數會響應

 private void KepGroup_AsyncWriteComplete(int TransactionID, int NumItems, ref Array ClientHandles, ref Array Errors)
        {
            try
            {
                for (int i = 1; i <= NumItems; i++)
                {
                    int error = int.Parse(Errors.GetValue(i).ToString());
                    int handle = int.Parse(ClientHandles.GetValue(i).ToString());
                    string key = dtOpcToPlc.First(o => o.Value.ItemHandle == handle).Key;
                    if (error == 0)
                    {
                        dtOpcToPlc[key].IsWriteOk = true;
                    }
                }
            }
            catch { }
        }

其它的一些基本連接方法(ConnectRemoteServer、GetLocalServer、RecurBrowse、SetGroupProperty等),是引用了百度上其它網友的案例,就不一一描述了。

7、總結

  OPC的dll提供了很多接口,相對調用簡單,只需要根據項目來作簡單修改。對於掉線異常重連,則需要根據實際調試案例來處理就行了,這個需要花一些時間來測試。

  這案例只測試了Omron PLC的通訊連接,其它PLC尚未進行實際測試。

  

C#創建OPC Client來訪問OPC server