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