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尚未進行實際測試。
C#創建OPC Client來訪問OPC server