C#創建OPC Client來訪問OPC server


  最近一個項目,需要跟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尚未進行實際測試。

  


免責聲明!

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



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