编辑
2025-11-30
C#
00

目录

引言
开发环境准备
关键代码解析
服务器发现与连接
创建订阅组并添加数据项
异步读取数据
异步写入数据
完整例子
注意事项
总结

引言

OPC(OLE for Process Control)数据访问(DA)是工业自动化中常用的通信协议,用于在不同软件和硬件系统之间交换实时数据。本文将详细介绍如何使用C#和OpcClientSdk实现OPC DA的异步读写操作。

开发环境准备

在开始之前,需要具备以下环境:

  • Visual Studio
  • .NET Framework
  • OpcClientSdk类库
  • OPC DA服务器

关键代码解析

服务器发现与连接

C#
// 发现可用的OPC DA服务器 private void Form1_Load(object sender, EventArgs e) { List<OpcServer> servers = OpcDiscovery.GetServers(OpcSpecification.OPC_DA_20); if (servers != null && servers.Count > 0) { foreach (OpcServer server in servers) { // 将服务器名称添加到下拉列表 cboServer.Items.Add(server.ServerName); } } } // 连接到选定的OPC服务器 private void btnConnect_Click(object sender, EventArgs e) { try { // 设置等待光标 Cursor = Cursors.WaitCursor; // 创建OPC服务器连接URL OpcUrl opcUrl = new OpcUrl(OpcSpecification.OPC_DA_20, OpcUrlScheme.DA, cboServer.Text); // 连接到OPC服务器 _OpcDaServer.Connect(opcUrl, null); } catch (OpcResultException exe) { MessageBox.Show(exe.Message, "连接错误", MessageBoxButtons.OK, MessageBoxIcon.Warning); } }

创建订阅组并添加数据项

C#
private void btnAddItem_Click(object sender, EventArgs e) { try { // 配置订阅组状态 TsCDaSubscriptionState groupState = new TsCDaSubscriptionState { Name = "MyGroup", // 组名 ClientHandle = "test", Deadband = 0, // 死区 UpdateRate = 1000, // 更新周期(毫秒) KeepAlive = 10000 // 保持活跃时间 }; // 创建订阅组 _pOpcGroup = (TsCDaSubscription)_OpcDaServer.CreateSubscription(groupState); // 配置数据项 TsCDaItem[] items = new TsCDaItem[] { new TsCDaItem { ItemName = txtOpcItem.Text, // OPC数据项名称 ClientHandle = 100 // 客户端句柄 } }; // 添加数据项到订阅组 _arAddedItems = _pOpcGroup.AddItems(items); } catch (Exception ex) { MessageBox.Show(ex.Message, "错误", MessageBoxButtons.OK, MessageBoxIcon.Exclamation); } }

异步读取数据

C#
private void btnRead_Click(object sender, EventArgs e) { // 异步读取数据项 OpcItemResult[] res = _pOpcGroup.Read( _arAddedItems, // 待读取的数据项 100, // 超时时间 new TsCDaReadCompleteEventHandler(ReadCompleteHandler), // 回调方法 out _ITRequest // 请求句柄 ); if (res[0].Result.IsError()) { MessageBox.Show("读取操作失败: " + res[0].Result.Description(), "错误"); } } // 读取完成回调方法 private void ReadCompleteHandler(object requestHandle, TsCDaItemValueResult[] e) { if (e[0].Result.IsSuccess()) { // 处理读取的数据 string sReadResult = $"Value: {e[0].Value}\n" + $"Quality: {e[0].Quality}\n" + $"TimeStamp: {e[0].Timestamp}"; // 线程安全地更新UI txtLog.Invoke(new addTextFromThreadDelegate((x) => { txtLog.AppendText(x + "\n"); }), new object[] { sReadResult }); } }

异步写入数据

C#
private void btnWrite_Click(object sender, EventArgs e) { // 准备写入的数据 TsCDaItemValue[] writeValues = new TsCDaItemValue[] { new TsCDaItemValue { ServerHandle = _arAddedItems[0].ServerHandle, Value = txtWrite.Text } }; // 异步写入数据 OpcItemResult[] res = _pOpcGroup.Write( writeValues, 321, new TsCDaWriteCompleteEventHandler(WriteCompleteCallback), out _ITRequest ); } // 写入完成回调方法 private void WriteCompleteCallback(object requestHandle, OpcItemResult[] e) { try { MessageBox.Show("写入操作完成: " + e[0].Result.Description(), "写入"); } catch (Exception ex) { MessageBox.Show(ex.Message, ex.Source, MessageBoxButtons.OK, MessageBoxIcon.Exclamation); } }

完整例子

C#
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace AppOpcDaAsync { using System; using System.Threading; using OpcClientSdk; using OpcClientSdk.Da; namespace OpcDaAsyncDemo { public class OpcDaAsyncClient { // OPC服务器连接对象 private TsCDaServer _server; // 数据订阅组 private TsCDaSubscription _subscription; // 同步等待句柄 private ManualResetEvent _connectionComplete = new ManualResetEvent(false); private ManualResetEvent _operationComplete = new ManualResetEvent(false); /// <summary> /// 连接OPC服务器 /// </summary> /// <param name="serverUrl">服务器地址</param> public void Connect(string serverUrl) { try { Console.WriteLine($"开始连接服务器: {serverUrl}"); // 创建服务器实例 _server = new TsCDaServer(); // 连接服务器 _server.Connect(serverUrl); // 获取服务器状态 OpcServerStatus status = _server.GetServerStatus(); Console.WriteLine($"服务器状态: {status.ServerState}"); Console.WriteLine($"服务器版本: {status.VendorInfo}"); // 创建订阅组 CreateSubscription(); // 标记连接完成 _connectionComplete.Set(); Console.WriteLine("服务器连接成功"); } catch (OpcResultException ex) { Console.WriteLine($"连接失败 - OPC错误: {ex.Message}"); _connectionComplete.Set(); } catch (Exception ex) { Console.WriteLine($"连接失败 - 未知错误: {ex.Message}"); _connectionComplete.Set(); } } /// <summary> /// 创建订阅组 /// </summary> private void CreateSubscription() { try { // 配置订阅组状态 TsCDaSubscriptionState subscriptionState = new TsCDaSubscriptionState { Name = "AsyncSubscription", Active = true, UpdateRate = 500, // 500毫秒更新一次 Deadband = 0 // 不启用死区 }; // 创建订阅组 _subscription = (TsCDaSubscription)_server.CreateSubscription(subscriptionState); // 注册事件处理器 _subscription.DataChangedEvent += OnDataChangedEvent; // 添加监控项 AddItems(); } catch (Exception ex) { Console.WriteLine($"创建订阅组失败: {ex.Message}"); } } /// <summary> /// 添加监控项 /// </summary> private void AddItems() { try { // 定义要监控的项目 TsCDaItem[] items = new TsCDaItem[] { new TsCDaItem { ItemName = "Channel.Device.L1", ClientHandle = 100, Active = true, ActiveSpecified = true }, new TsCDaItem { ItemName = "Channel.Device.L2", ClientHandle = 200, Active = true, ActiveSpecified = true }, new TsCDaItem { ItemName = "Channel.Device.S1", ClientHandle = 200, Active = true, ActiveSpecified = true } }; // 添加项目到订阅组 TsCDaItemResult[] results = _subscription.AddItems(items); // 检查添加结果 foreach (var result in results) { if (result.Result.IsError()) { Console.WriteLine($"添加项目失败: {result.ItemName} - {result.Result.Description()}"); } else { Console.WriteLine($"成功添加项目: {result.ItemName}"); } } } catch (Exception ex) { Console.WriteLine($"添加监控项失败: {ex.Message}"); } } /// <summary> /// 异步读取数据 /// </summary> public void AsyncRead() { try { // 等待连接完成 _connectionComplete.WaitOne(); // 准备读取的项目 TsCDaItem[] readItems = new TsCDaItem[] { new TsCDaItem { ItemName = "Channel.Device.L1", ClientHandle = 300, Active = true, // 设置数据项为活动状态 ActiveSpecified = true, // 启用 Active 设置 } }; // 创建请求句柄 object requestHandle = Guid.NewGuid().ToString(); // 正确的异步读取调用 IOpcRequest request = null; var resultRequest = _subscription.Read( readItems, // 多个要读取的项目 requestHandle, // 请求句柄 new TsCDaReadCompleteEventHandler(OnReadCompleteEvent), out request // 输出请求对象 ); // 检查读取结果 if (resultRequest != null) { return; } Console.WriteLine("异步读取已发起"); } catch (Exception ex) { Console.WriteLine($"异步读取失败: {ex.Message}"); } } /// <summary> /// 异步写入数据 /// </summary> public void AsyncWrite() { try { // 等待连接完成 _connectionComplete.WaitOne(); // 准备写入的数据 TsCDaItemValue[] writeItems = new TsCDaItemValue[] { new TsCDaItemValue { ItemName = "Channel.Device.L1", Value = 42, ClientHandle = 500 } }; // 发起异步写入 _subscription.Write( writeItems, 600, new TsCDaWriteCompleteEventHandler(OnWriteCompleteEvent), out IOpcRequest request ); Console.WriteLine("异步写入已发起"); } catch (Exception ex) { Console.WriteLine($"异步写入失败: {ex.Message}"); } } /// <summary> /// 断开连接 /// </summary> public void Disconnect() { try { if (_subscription != null) { _subscription.Dispose(); } if (_server != null) { _server.Disconnect(); _server.Dispose(); } Console.WriteLine("已成功断开连接"); } catch (Exception ex) { Console.WriteLine($"断开连接时发生错误: {ex.Message}"); } } /// <summary> /// 读取完成事件处理 /// </summary> /// <param name="clientHandle">客户端句柄</param> /// <param name="results">读取结果数组</param> public void OnReadCompleteEvent( object clientHandle, TsCDaItemValueResult[] results) { try { Console.WriteLine($"Read Operation Completed. Client Handle: {clientHandle}"); foreach (var result in results) { Console.WriteLine("-----------------------------------"); Console.WriteLine($"Item Name: {result.ItemName}"); // 检查读取是否成功 if (result.Result.IsSuccess()) { // 处理数组类型 if (result.Value.GetType().IsArray) { Console.WriteLine("Value Type: Array"); if (result.Value is Array arrayValue) { Console.WriteLine($"Array Length: {arrayValue.Length}"); for (int i = 0; i < arrayValue.Length; i++) { Console.WriteLine($" Index {i}: {arrayValue.GetValue(i)}"); } } } else { Console.WriteLine($"Value: {result.Value}"); Console.WriteLine($"Value Type: {result.Value.GetType().Name}"); } // 时间戳和质量信息 Console.WriteLine($"Timestamp: {result.Timestamp:yyyy-MM-dd HH:mm:ss.fff}"); Console.WriteLine($"Quality: {result.Quality}"); // 质量码分析 AnalyzeQuality(result.Quality); } else { Console.WriteLine($"Read Failed for Item: {result.ItemName}"); Console.WriteLine($"Error: {result.Result.Description()}"); } } } catch (Exception ex) { Console.WriteLine($"Error in Read Complete Event: {ex.Message}"); Console.WriteLine($"Stack Trace: {ex.StackTrace}"); } finally { Console.WriteLine("Read Operation Event Processing Completed"); } } /// <summary> /// 写入完成事件处理 /// </summary> /// <param name="clientHandle">客户端句柄</param> /// <param name="results">写入结果数组</param> public void OnWriteCompleteEvent( object clientHandle, OpcItemResult[] results) { try { Console.WriteLine($"Write Operation Completed. Client Handle: {clientHandle}"); foreach (var result in results) { Console.WriteLine("-----------------------------------"); Console.WriteLine($"Item Name: {result.ItemName}"); // 检查写入是否成功 if (result.Result.IsSuccess()) { Console.WriteLine("Write Operation Successful"); } else { Console.WriteLine($"Write Failed for Item: {result.ItemName}"); Console.WriteLine($"Error: {result.Result.Description()}"); } } } catch (Exception ex) { Console.WriteLine($"Error in Write Complete Event: {ex.Message}"); Console.WriteLine($"Stack Trace: {ex.StackTrace}"); } finally { Console.WriteLine("Write Operation Event Processing Completed"); } } /// <summary> /// 数据变更事件处理 /// </summary> /// <param name="subscriptionHandle">订阅句柄</param> /// <param name="requestHandle">请求句柄</param> /// <param name="values">变更的数据值数组</param> public void OnDataChangedEvent( object subscriptionHandle, object requestHandle, TsCDaItemValueResult[] values) { try { // 输出请求句柄信息 if (requestHandle != null) { Console.WriteLine($"DataChange for RequestHandle: {requestHandle}"); } // 处理数据变更逻辑 foreach (var value in values) { // 检查操作是否成功 if (value.Result.IsSuccess()) { // 处理数组类型 if (value.Value.GetType().IsArray) { Console.WriteLine($"Array Item: {value.ItemName}"); if (value.Value is Array arrayValue) { for (int i = 0; i < arrayValue.Length; i++) { Console.WriteLine($" Index {i}: {arrayValue.GetValue(i)}"); } } } else { // 处理单一值类型 Console.WriteLine($"Item: {value.ItemName}"); Console.WriteLine($"Value: {value.Value}"); } // 详细的质量和时间戳信息 Console.WriteLine($"Timestamp: {value.Timestamp:yyyy-MM-dd HH:mm:ss.fff}"); Console.WriteLine($"Quality: {value.Quality}"); // 质量码详细分析 AnalyzeQuality(value.Quality); } else { // 处理失败的数据项 Console.WriteLine($"Item {value.ItemName} read failed."); Console.WriteLine($"Error: {value.Result.Description()}"); } } } catch (Exception ex) { // 异常处理 Console.WriteLine($"Error in DataChange Event: {ex.Message}"); Console.WriteLine($"Stack Trace: {ex.StackTrace}"); } finally { Console.WriteLine("Data Change Event Processing Completed"); } } /// <summary> /// 质量码详细分析方法 /// </summary> /// <param name="quality">OPC质量码</param> private void AnalyzeQuality(TsCDaQuality quality) { Console.WriteLine("Quality Details:"); Console.WriteLine($" Quality Bits: {quality.QualityBits}"); Console.WriteLine($" Limit Bits: {quality.LimitBits}"); Console.WriteLine($" Vendor Bits: {quality.VendorBits}"); // 质量码详细判断 switch (quality.QualityBits) { case TsDaQualityBits.Good: Console.WriteLine(" Status: Excellent Data Quality"); break; case TsDaQualityBits.GoodLocalOverride: Console.WriteLine(" Status: Good Local Override"); break; case TsDaQualityBits.Bad: Console.WriteLine(" Status: Bad Quality - Failure"); break; case TsDaQualityBits.Uncertain: Console.WriteLine(" Status: Uncertain Quality"); break; default: Console.WriteLine(" Status: Unknown Quality"); break; } } } } }

image.png

注意事项

  1. 异步操作需要使用回调方法处理结果
  2. 涉及UI更新时需要使用Invoke确保线程安全
  3. 错误处理很重要,要捕获并适当处理可能的异常

总结

本文详细演示了使用OpcClientSdk进行OPC DA异步读写的完整流程,包括服务器发现、连接、数据项订阅、异步读取和写入。通过异步机制,可以有效提高工业通信系统的响应性和性能。

希望这篇文章对您有帮助!如有任何疑问,欢迎随时询问。

本文作者:技术老小子

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!