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