OPC(OLE for Process Control)数据访问(DA)是工业自动化中常用的通信协议,用于在不同软件和硬件系统之间交换实时数据。本文将详细介绍如何使用C#和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;
}
}
}
}
}

Invoke确保线程安全本文详细演示了使用OpcClientSdk进行OPC DA异步读写的完整流程,包括服务器发现、连接、数据项订阅、异步读取和写入。通过异步机制,可以有效提高工业通信系统的响应性和性能。
希望这篇文章对您有帮助!如有任何疑问,欢迎随时询问。
本文作者:技术老小子
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!