首页 / 资讯中心 / 文章详情

UcAsp.Opc实战:C#统一访问OPC DA与UA的完整指南

UcAsp.Opc实战:C#统一访问OPC DA与UA的完整指南 ★ FEATURED ARTICLE
简介一套面向工业自动化与 .NET 开发者的开源 OPC 通信实现聚焦 OPC DA 与 OPC UA 两大标准可用于快速搭建客户端/服务器交互、PLC/SCADA 数据采集及设备集成。压缩包共54个文件以 C# 源码为主32个.cs同时包含可直接引用的 DLL 库模块、项目与配置文档以及 OPC Core Components Redistributable (x64).msi 安装包整体约2.26MB结构精简清晰。内容覆盖 OpcItem、OpcGroup、OpcClient 等核心类提供 .NET 4.0/4.6 多版本工程便于对比学习。已有520人学习下载适合正在学习 OPC 协议、需要参考成熟客户端封装或自研网关的工程师。通过阅读源码与示例可掌握 OPC DA 的属性/组/项模型、数据订阅与回调机制同时了解 OPC UA 客户端库的引入方式为后续开发工业接口、避免重复造轮子提供直接借鉴。1. 从 UcAsp.Opc 说起一个能同时摸到 OPC DA 和 UA 的 C# 开源包做工业上位机的人多半绕不开 OPC DA。只要你接过西门子、施耐德、罗克韦尔或者国产 PLC 的数据DA 几乎是最常见的老协议。UcAsp.Opc 是一个 C# 实现的开源 OPC 客户端库master 分支源码包里有 DA 和 UA 两套访问路径连 DLL 和示例工程都给你备齐了。这篇文章把它彻底拆一遍工程里哪些文件有用、怎么在三十分钟内跑通第一次读点、读写权限和订阅事件怎么调、以及我在 x64 环境里踩过的坑。适合刚接手设备的上位机工程师也适合想快速做 OPC 采集网关的原型开发者。源码本身不大但里面藏着不少能直接拷到生产环境里的细节。2. 先搞清 DA 和 UA为什么这个包把两套接口都装进了同一个命名空间2.1 COM 时代的 DA 和它的读写模型OPC DA 定义的是客户端和服务器之间实时数据访问的规范。它最早建立在微软的 COM/DCOM 之上核心模型是三个东西服务器Server、组Group、数据项Item。服务器管连接组管一批数据项的刷新节奏数据项指向设备里的一个真实点位比如一条产线上某个模拟量。你在 UcAsp.Opc 源码里能看到 OpcServer、OpcGroup、OpcItem 这些类命名就是对这套模型的映射。实际读写时客户端先把 Item 加入一个 Group然后告诉 Group 用多快的周期刷新刷新结果会以回调或事件的形式送到客户端。这套模型很老但至今仍大量嵌在西门子 WinCC、罗克韦尔 FactoryTalk 和无数国产组态软件里。原因很简单它稳定而且只解决一件事——把数据从设备侧搬到应用侧。2.2 UA 的出现从 COM 到 TCP/IP 的迁移OPC DA 的毛病也很明显依赖 COM 注册DCOM 跨域配置深似海而且只能在 Windows 上跑。于是 OPC 基金会推出了 UA全称 Unified Architecture。UA 不再依赖 COM改用 TCP/IP 和二进制或 HTTPS 传输还带了一套完整的证书安全体系能跑在 Linux、嵌入式设备上也方便和云平台对接。UcAsp.Opc 的源码包里同时出现了 Opc.Ua.Client.dll、Opc.Ua.Core.dll、Opc.Ua.Configuration.dll 这些程序集说明作者并不只做了 DA而是把 UA 客户端也打包进来了。这对实际选型很重要如果你的现场设备有新款 PLC 或支持 UA 的采集器可以直接用同一个命名空间下的 API 去连不需要再引一套别的库。2.3 工程结构从 sln 到 lib 一眼看清解压 UcAsp.Opc-master.zip 后你能看到的是一个完整的 Visual Studio 解决方案不是零散代码。我先按平时拆源码的习惯把根目录文件捋了一遍整理成下表文件/目录作用我的判断UcAsp.OPC.sln解决方案文件直接用 VS 打开先看这个UcAsp.Opc.Net4.6 / UcAsp.Opc.Net4.0两个版本的项目工程一个给 .NET Framework 4.6一个给 4.0老系统也能用OpcClient.cs / OpcItem.cs / OpcGroup.cs核心客户端、数据项、分组类理解和改动都在这里Node.cs / INode.cs节点模型UA 侧用得多做 DA 可以少看ConvertToT.cs / AccessRights.cs / OpcStatus.cs类型转换、权限、状态枚举读写时要配合lib 目录OpcComRcw.dll、OpcNetApi.dll、OpcNetApi.Com.dll、Opc.Ua.*.dll运行依赖必须带上OPC Core Components Redistributable (x64).msiOPC 基础 COM 运行库装它才能连 DA见第 5 章这里有几个细节值得多说一句。lib 里同时有 OpcNetApi.dll 和 OpcNetApi.Com.dll前者是 OpcNetApi 的 .NET 封装后者是面向 COM 互操作的部分而 OpcComRcw.dll 是 COM 运行库的 RCW。如果你只想用 UA那 OpcComRcw.dll 可以不用引但工程里默认是 DA 和 UA 在一起编译的所以最好保持原样只在部署时按需裁剪。README.txt 里应该也写了编译环境的注意点我建议先照着跑一遍自带的 OpcTest 单元测试确认环境 OK 再动自己的代码。打开 OpcClient.cs 你会发现构造函数的入口很克制客户端、服务器地址、客户端选项。而 Factory 这个类型专门负责把协议栈的差异隐藏起来——你传一个 opcda:// 开头的地址它就走 DA 的 COM 链路传 opc.tcp:// 开头的地址它就切到 UA 的 TCP 链路。这样的设计在二三十个项目的迭代里非常实用业务层可以只面对一个 IClient 接口底层协议是 DA 还是 UA 不影响上游采集逻辑。对于要开发采集网关的团队这是最值得参考的一段代码。再说两个容易被忽略的类型。ClientOptions 是客户端会话层面的配置集合里面可以设置请求超时、操作超时这类跟网络稳定性强相关的参数。现场网络抖动大时我一般把超时从默认的 30 秒调到 60 秒这个字段在生产环境里比连接字符串还影响体验。OpcStatus 则是一个状态枚举它把 DA 的 OPC_STATUS 和 UA 的 ServerState 统一成了一个值调试时直接打日志就能判断服务器跑没跑起来、组有没有激活。这两个类都在 UcAsp.Opc 根命名空间下配合 IClient 接口能让上层业务完全不感知底层协议差异。3. 把 UcAsp.Opc 跑起来从引用 DLL 到完成第一次数据读取3.1 添加引用与运行环境准备这一步决定后面能不能顺利编译我把它拆成三个动作。第一安装 OPC Core Components Redistributable。包里那个 x64 的 msi 不要跳过OPC DA 的 COM 组件注册全靠它。很多新手编译没问题一跑就报“没有注册类”九成是没装这个。第二按目标框架选择项目。如果你的设备系统是 Windows 7 .NET Framework 4.5那用 Net4.0 工程更保险如果系统是新装的 Win10/Server 2019直接上 Net4.6。它们的 API 基本一致区别主要在依赖的程序集版本。第三在 Visual Studio 里创建一个控制台或 WinForms 工程然后按下面代码引用// 添加 DLL 引用在解决方案里右键“引用”-“添加引用”-“浏览” // 导航到 UcAsp.Opc-master/lib 目录至少勾选 // OpcComRcw.dll // OpcNetApi.dll // OpcNetApi.Com.dll // 如果用 UA再加 Opc.Ua.Client.dll、Opc.Ua.Core.dll、Opc.Ua.Configuration.dll这里有个常见误区不要一股脑把 lib 下所有文件都拷到输出目录。过会儿在第 5 章我会专门讲一次我因为全拷引发的事故。这里只需要知道DA 和 UA 是两个独立程序集组按你的协议选一组即可。3.2 创建 OpcClient 并连接 DA 服务器引用就绪后连接一个 DA 服务器只需要很短的代码。我一般会用一个本地的模拟器做开发最常用的是 “Matrikon OPC Simulation”它的服务器地址是opcda://localhost/Matrikon.OPC.Simulation。然后写using UcAsp.Opc; var client new OpcClient(opcda://localhost/Matrikon.OPC.Simulation); client.Connect(); if (client.IsConnected) { Console.WriteLine(连接成功当前状态 client.Status); }代码说明OpcClient接收地址字符串内部的Factory会根据opcda://前缀选择 DA 协议栈。Connect()是同步方法返回后不代表一定能读数据只能代表 COM 链路已建立真正的点位访问需要继续构造数据项。IsConnected和StatusOpcStatus 枚举用来判断连接是否存活调试时很关键。3.3 读取一个点位从 OpcItem 到 OpcItemValue连接状态正常后创建一个OpcItem指向服务器里的一个真实标签。Matrikon 模拟器里有现成的 “Bucket Brigade” 区域里面的点会自己跳动适合验证读取。var item new OpcItem(Bucket Brigade.Int1); var value client.Read(item); Console.WriteLine($值{value.Value}质量{value.Quality}时间戳{value.Timestamp});Read返回的是OpcItemValue类型它同时携带数值、质量戳和时间戳这正好对应 OPC DA 的 VQT 三元组。很多新手只拿.Value就完事我建议质量戳也要判断Quality等于 192 表示质量好Good小于 192 要警惕设备侧异常。如果读出来是null不用急着怀疑代码。先确认标签路径有没有写对大小写是否敏感以及服务器上的 Item 是不是确实存在。你可以用 Matrikon 自带的 OPC Explorer 去浏览器看树形结构把路径原样抄过来能少走很多弯路。3.4 订阅式读取用事件拿实时值而不是轮询DA 协议本身是支持异步通知的你先把数据项“订阅”到某个组里服务器按设定周期检测变化有变化就推给客户端。UcAsp.Opc 把这个机制包装成了DataChanged事件。我最早期的项目就是靠Thread.Sleep加Read硬轮询200 个点、100ms 周期直接把 CPU 吃满。后来全改成订阅CPU 占用降到 5% 以内。订阅的写法大致是var client new OpcClient(opcda://localhost/Matrikon.OPC.Simulation); client.DataChanged Client_DataChanged; client.Connect(); client.AddItems(new ListOpcItem { new OpcItem(Bucket Brigade.Int1), new OpcItem(Bucket Brigade.Int2), new OpcItem(Bucket Brigade.Int3) }); private void Client_DataChanged(object sender, ItemDataEventArgs e) { foreach (var v in e.Values) { Console.WriteLine(${v.ItemName} {v.Value} Quality{v.Quality}); } }说明AddItems传入一组OpcItem事件参数ItemDataEventArgs.Values里能拿到每个点位的最新 VQT。注意DataChanged是在后台线程触发的所以如果你要在它里面更新 WinForm 控件记得用BeginInvoke封送到 UI 线程。这个点很容易写出一闪而过的诡异崩溃属于经典排查点。批量请求也是一个值得提的能力。OPC DA 的组机制天生就适合批量比如你要一次读 100 个温度值用AddItems加 100 个 Item服务器会在一个刷新周期内把数据一次性推回来比单独发 100 次请求效率高得多。UcAsp.Opc 的Read也有重载支持传一组 Item 返回一组OpcItemValue适合那些不需要订阅、只做周期快照的场景。4. 参数细节与调试读不到值先查这几处4.1 OpcItem、Node 和路径地址项格式决定能不能读到OPC DA 和 UA 在地址表示上差异很大。DA 用的是服务器名.ItemID比如Bucket Brigade.Int1这是一个扁平的字符串路径并且很多服务器对大小写敏感。UA 用的是 NodeId通常带一个命名空间索引比如ns2;sTag1结构更像一个结构化引用。UcAsp.Opc 把这两种地址统一到了Node和OpcItem两个类里。看源码你会发现OpcItem有一个字符串ItemName而Node则用来表达 UA 的节点属性。调用client.Read(item)时如果地址前缀是 DA就直接把ItemName当作 ItemID 发给 COM 层如果是 UA则会把 Node 信息转成 UA 的 ReadValueId。所以当你从现场拿到一台新设备第一步永远是打开服务器的浏览器把真实的点位路径抠出来而不是凭 PLC 程序里的符号名猜。Kepware、Matrikon 这些服务器都提供类似 “Tag Browser” 的工具照着挑路径能避开 80% 的 “读不到” 问题。4.2 AccessRights 与读写权限能读不等于能写你在源码里能看到AccessRights枚举它标记了一个数据项是只读、可写还是可读写。这是新手最容易忽略的参数。即使 OPC 服务器暴露了某个 Item也不代表客户端一定能写如果设备侧配置了只读Write请求会返回失败而你看到的异常信息往往很笼统。常见做法是写一个简单的自检流程var item new OpcItem(Bucket Brigade.Int1) { AccessRights AccessRights.ReadWrite }; var result client.Write(item, 100); if (result.ResultCode 0) { Console.WriteLine(写入成功); } else { Console.WriteLine(写入失败代码 result.ResultCode); }代码说明AccessRights在构造时设置只是给客户端一个期望真正能不能写取决于服务器侧的安全配置。Write返回的Result里有ResultCode0 通常表示成功非零值对应 COM HRESULT 或 UA 状态码需要查对应文档。我在实际项目里见过因为服务器上启用了“工程师模式”导致所有客户端可读不可写最后是上位机侧被锁定才发现的这类权限问题最坑。4.3 连接状态与 OpcStatus判断是断开还是没数据调试过程中你需要分清楚三种状态连接正常没有数据、连接断开、以及连接正常但点位不存在。UcAsp.Opc 里OpcStatus枚举会告诉你服务器或组的状态。我通常这样用if (client.IsConnected) { if (client.Status OpcStatus.Running) { // 正常连接继续读 } else { Console.WriteLine(连接未就绪 client.Status); } } else { Console.WriteLine(已经断开需要重连); }这个判断在长时间运行的采集服务里要定时执行。OPC DA 基于 COM服务器的组会按心跳机制周期性检测客户端存活如果网络闪断超过一定时间客户端侧不会立刻感知需要主动检查Status。另外OpcItemValue.Quality是另一个信号如果值为 0 或负值而且质量是 Bad那不是连接断了而是设备侧的通道断了。4.4 类型转换与 ConvertToT值不是 object 就完事OPC 服务器返回的值是 COM 的 VARIANT 类型到了 .NET 就成了 object。如果你直接从OpcItemValue.Value里拿它去做加法就会撞上运行时强转异常。UcAsp.Opc 项目里专门做了一个ConvertToT类用来把 object 转成指定泛型类型。我一般这样用var temp client.Read(item).Value; float realValue ConvertToT.Tofloat(temp);ConvertToT内部先处理 DBNull再处理字符串和数值类型的相互转换遇到枚举类型也能转成对应的整数值。注意它不会帮你做单位换算也不会把12.3这种字符串自动变成 float 之外的任何业务对象。用好它至少能省掉你在每个点位上都写一遍Convert.ToDouble的重复劳动。5. 避坑指南我在这个包里踩过的五个坑5.1 坑一x64 下 COM 组件注册失败现象项目编译没问题一跑Connect()就抛 “Retrieving the COM class factory for component with CLSID ... failed”或者直接提示“没有注册类”。原因OPC DA 的互操作要依赖 OPC Core Components 在系统里注册的 COM 组件。我一开始以为装了包里那个OPC Core Components Redistributable (x64).msi就万事大吉后来发现它只注册了 64 位 COM而我的测试程序因为引用关系被编译成了 x86。32 位进程访问不到 64 位注册的 COM 组件。解决打开项目属性把“平台目标”从AnyCPU改成x64或者到 NuGet 里找对应 x64 的 OPC Core 安装包。如果你必须保留 x86 进程比如要调用一个老的 32 位 DLL那就需要安装 x86 版的 OPC Core 组件并把平台目标改成x86。我个人的习惯是统一成 x64新机器都装 x64 组件目标平台固定 x64省得两套注册表来回切换。5.2 坑二UA 连接提示找不到证书或证书不可用现象用opc.tcp://地址连接 UA 服务器时客户端抛BadSecurityModeRejected或者CertificateUntrusted。原因UA 的安全模型要求客户端和服务器双向信任证书。UcAsp.Opc 的 UA 部分实现了标准流程但它默认生成的客户端证书如果没被服务器添加信任握手就失败。很多模拟器第一次连接时会弹一个“是否信任客户端证书”的确认框如果后台无人值守点击连接就一直失败。解决在开发机上跑一次 UA 连接确认 UA 服务器端的证书列表里已经添加了 UcAsp 客户端生成的证书。另外检查Opc.Ua.Configuration.dll的配置入口它会加载ApplicationInstance的证书路径。你也可以在连接参数里暂时把安全策略改成None来做调试但生产环境千万不要关安全工业现场认证还是得开着。5.3 坑三订阅事件不触发数据却存在现象DataChanged写了AddItems也加了点但程序运行后事件就是一次都不触发用Read却能读到值。原因这是 UcAsp.Opc 的一个经典坑。订阅后客户端需要激活订阅Active也就是让 Group 处于“开始刷新”的状态。某些版本的封装里AddItems只负责注册数据项并不会自动把 Group 的刷新打开如果你漏了打开状态服务器根本不会往下推数据。解决看完OpcClient.cs的源码我注意到它对外暴露了一个类似Start或Group.Active true的入口。我的做法是在AddItems之后显式把客户端设置为激活状态client.Active true; // 或者调用 client.Start();解决后事件立刻开始往外蹦。如果依然不触发再检查服务器端的组死区和变化率设置有些模拟器只有“值变化超过死区才发送”你把死区设得很大自然收不到事件。5.4 坑四DA 路径里的特殊字符被转义现象从 OPC 浏览器里复制的路径是Line1.Flow.Temperature代码里一模一样却报Exception from HRESULT: 0x80040004或E_FAIL。原因某些 OPC 服务器的 ItemID 包含空格、连字符或点号而 UA 的转换层会尝试把它当 NodeId 解析导致地址被拆错。UcAsp.Opc 的OpcItem构造函数有一个FullyQualified相关的参数作用就是决定要不要对 Item 路径做标准化处理默认值在部分服务器上会出错。解决创建OpcItem时显式传入完整路径参数或者用重载构造var item new OpcItem(Line1.Flow.Temperature, true);这里第二个参数表示“这是一个完整限定名”告诉库不要自行拼接或格式化。如果你的点位路径是从配置项读进来的建议先打印item.ItemName确认没有前后空格也别用Trim()之外的多余处理。这条看着小但现场查起来特别费时间通常会被误认为是网络问题。5.5 坑五把 lib 下所有 DLL 都拷进输出目录导致版本冲突现象程序启动时报Could not load file or assembly Opc.Ua.Core, Version1.2.x之类的程序集加载异常或者明明引用了新库却跑在老版本上。原因lib 目录里同时存在多个目标框架的程序集比如 Net4.0 和 Net4.6 两套还有不同版本的 Opc.Ua.*。我图省事把整个 lib 目录设成“复制到输出目录”结果运行时加载到了错误的程序集版本校验失败。解决只复制你正在使用的那份匹配的程序集。DA 项目只要 OpcComRcw.dll、OpcNetApi.dll、OpcNetApi.Com.dllUA 项目只要 Opc.Ua.Core.dll、Opc.Ua.Client.dll、Opc.Ua.Configuration.dll。工程属性里把“本地复制”设为 True 可以保留依赖自动拷贝但前提是引用时选对了文件。我后来专门写了一个.targets文件在构建时按配置过滤一劳永逸。6. 进阶用这个包做数据归档与断线重连的一个可行套路6.1 用队列把高频订阅事件变成低频落盘订阅事件在点位多的时候会非常密直接写数据库或者日志会被 IO 拖死。我一般会在事件处理函数里把数据投到一个有界队列再用一个独立落盘线程批量处理BlockingCollectionOpcItemValue buffer new BlockingCollectionOpcItemValue(10000); void Client_DataChanged(object sender, ItemDataEventArgs e) { foreach (var v in e.Values) { buffer.Add(v); } } // 独立线程里循环取数据 Task.Run(() { foreach (var v in buffer.GetConsumingEnumerable()) { // 批量写入数据库或写入 MQTT 网关 } });这样事件线程只做入队落盘速度由消费端控制缓冲区满了还能通过Add的阻塞动画实现背压避免内存无限膨胀。6.2 断线重连与状态监测OPC DA 的 DCOM 链路在工厂网络里经常被交换机或安全软件莫名掐断。我的套路是启动一个定时器每秒检查一次IsConnected断开就尝试重连但重连之前必须先把旧连接释放干净不然会残留 COM 引用导致内存泄漏if (!client.IsConnected) { try { client.Disconnect(); } catch { } client.Connect(); }注意这里重连的地址要原样复用不要 new 一个新 OpcClient。因为 UA 服务器的会话恢复需要复用原来的Client上下文否则服务器端会积累大量作废会话。6.3 验证拿模拟器当服务器跑完整流程无论你用 Matrikon OPC Simulation 还是 Kepware 做远端服务器验证流程都应该固定成四步连接服务器 → 浏览点位 → 订阅数据 → 写入测试。特别是写测试很多工程师只测读不测写结果设备联调时才发现写权限没配。我自己的习惯是用脚本把读、写、事件全部跑一遍确认QualityGood、写入后读回一致才放行打包。从那以后我每次接新项目都强制走一遍这套验证流程宁可模拟器上多花十分钟也不去现场做反复试验。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?
咨询建站