最近一个项目,需要跟PLC通讯,所以测试使用了OPC server。现主要记录使用C#编写的Client例程,其它方面不作详细描述。
第一步,OPC Server使用的是KEPServer 5版本,网上很多资料。安装完成后,它的配置页面如下图。配置中,我已配置了和Omron PLC连接的project,创建了访问PLC的area地址的几十个变量。具体配置根据不同PLC的信息对应配置就行了。
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尚未进行实际测试。
原文:https://www.cnblogs.com/GanSlide/p/9337608.html