简介这是一份面向工业自动化及数控系统集成场景的OPC UA客户端实例程序基于西门子官方源代码整理适用于需要与SINUMERIK 840Dsl数控系统进行数据采集、状态监控及参数读写的工程师也适合有一定C#基础和工业通信背景的开发者作为参考。压缩包共68个文件以C#源文件cs、工程与配置文件sln、csproj、config、界面资源png、ico、resx以及封装好的OPC UA动态库dll为主整体约1.29MB目录结构清晰便于直接打开与二次开发。已有646人学习是理解OPC UA信息模型、订阅服务、节点读写与安全通信机制的有效参考。程序中包含OpcUaHelper核心封装和OpcUaTest测试工程覆盖连接配置、节点遍历、数据订阅与调用服务等常见流程并附带多个示例页面和图标资源开发者可借助源码快速掌握与西门子数控系统的交互方法并在此基础上扩展实际项目所需的监控告警、历史数据记录等功能也可作为快速原型验证与教学演示的基础工程。 当前很多搞自动化、搞上位机、甚至搞物联网的朋友都绕不开一个词OPC UA。但真正动手去写一个OPC UA实例程序的时候不少人会被那一堆证书、配置、节点模型搞懵。我最早接触OPC UA时也被吓到了资料不少但大多数是协议概念真正能直接跑起来改一改就用的实例非常少。这篇文章就从一个最简单的实例程序说起把OPC UA的系统框架、服务端搭建、客户端读写、以及Node-RED可视化这些环节串起来。这套内容适合哪些人看如果你正在做设备数据采集、MES系统对接、工业物联网网关或者单纯想搞清楚PLC和上位机之间到底怎么通信那这篇内容可以参考一下。我自己实践下来OPC UA没有想象中那么玄乎关键是把服务端、客户端、节点寻址这几个核心点吃透后面都是套路。1. 先把OPC UA这件事想明白1.1 为什么偏偏是OPC UA在聊实例程序之前得先弄明白OPC UA到底解决了什么问题。过去的自动化系统里每个品牌的PLC有自己的一套协议西门子用S7、三菱用MC、Modbus又是另一套做上位机对接的时候往往要给每种设备写一个驱动。OPC UAOpen Platform Communications Unified Architecture就是来解决这种“百花齐放”的问题它像一个翻译官把设备的数据用统一的语义模型暴露出来不管设备底层的协议是什么上层系统只用一套方式就能访问。跟经典的OPC DA相比OPC UA是完全跨平台的不依赖Windows的COM/DCOM组件也能加密传输还能描述数据之间的层次关系。举个直观的例子以前用OPC DA读一个温度值拿到的就是一串数字用OPC UA读不仅拿到数值还知道这个数值属于哪个设备、哪个传感器、单位是什么、报警阈值是多少这就是信息模型的威力。所以现在很多新采购的设备直接原生支持OPC UA Server这就让数据采集标准化了很多。1.2 一个实例程序通常包含哪些零件一个完整的OPC UA实例程序至少包含两个角色服务端和客户端。服务端运行在设备侧或者网关侧把数据以节点Node的形式发布出来客户端运行在采集端负责连接服务端、浏览节点、读取数值、订阅变化。我们常说的“OPC UA实例程序”多数时候指的是一个聊胜于无的最小实现服务端内置几个模拟数据节点比如温度、压力客户端定时读一次再展示出来。别小看这个流程它把OPC UA最核心的几件事全串起来了端点配置、创建会话Session、节点寻址NodeId、读写操作、订阅机制。把这些弄懂了再去接真实PLC或者SCADA系统思路就清晰很多。我还要特别提醒一点OPC UA里的很多概念是有层次关系的。服务端对应一个物理设备或软件进程服务端里有地址空间AddressSpace里面挂着一棵节点树每个节点通过节点ID定位客户端和服务端的通信建立在Session之上而Session又建立在SecureChannel之上。画不出这个层次关系的时候先别急着写代码把架构理清楚再看具体实现能省不少弯路。2. 搭建一个最小可跑的OPC UA服务端2.1 语言和SDK怎么选OPC UA的SDK不少官方有C、.NET、Java版本生态相对成熟的还是C# .NET这个分支。主要原因是OPC基金会的官方示例和开源实现UA-.NETStandard就是基于.NET的出问题能在网上找到大量的讨论。如果你对C#不熟用Python也有freeopcua这种库可以做实验但我个人建议想深入理解OPC UA的话还是用C#过一遍官方示例更稳妥。这里我用的是开源库OPCFoundation.NetStandard.Opc.Ua通过NuGet直接装就可以。这个SDK把底层的通信、安全、节点管理都封装好了我们只需要写少量代码就能跑起一个服务端。2.2 服务端核心代码与配置我写了一个近乎最简的服务端实例完整逻辑几百行但核心只有几步。我们先看代码结构再看对应的配置说明。using System; using System.Threading.Tasks; using Opc.Ua; using Opc.Ua.Server; public class SimpleServer : StandardServer { protected override MasterNodeManager CreateMasterNodeManager( IServerInternal server, ApplicationConfiguration configuration) { // 这里可以注册自定义的节点管理器 return new MasterNodeManager(server, configuration, null, new CustomNodeManager(server, configuration)); } protected override ServerProperties LoadServerProperties() { return new ServerProperties { ApplicationName Simple OPC UA Server, ApplicationUri urn:myserver:simple, ProductUri urn:myserver:simple:product }; } public static async Task Main(string[] args) { var application new ApplicationInstance { ApplicationName Simple OPC UA Server, ApplicationType ApplicationType.Server }; // 加载配置文件这一步会读取证书、端点配置等 var config await application.LoadApplicationConfiguration(server.config.xml, silent: false); // 检查/创建应用证书 await application.CheckApplicationInstanceCertificates(false, CertificateFactory.DefaultLifeTime); var server new SimpleServer(); await server.Start(config); Console.WriteLine(Server started. Press any key to exit.); Console.ReadLine(); await server.Stop(); } }这段代码看起来简单但背后做了很多事。LoadApplicationConfiguration会读取一个XML配置文件里面定义了服务端监听哪个端口、支持哪些安全策略、证书位置在哪。我用的配置是监听opc.tcp://localhost:4840这是OPC UA的默认端口方便测试。2.3 没有PLC时怎么模拟数据在真实项目里服务端往往连着PLC或者传感器但做实例程序的时候手头未必有硬件。所以我在服务端里内置了一个自定义节点管理器动态创建几个模拟节点然后用一个定时器修改节点值模拟温度、压力这些变量在实时变化。自定义节点管理器的核心思路就是继承CustomNodeManager在构造函数里创建节点并实现CreateAddressSpace方法。为了给客户端提供数据我在节点管理里维护了一个字典键是节点ID值是当前的数值定时器每500毫秒刷新一次然后调用Server.NodeManager的相关方法通知上层。这样做的好处是你不需要任何硬件就能把一个完整的OPC UA环境拉起来用来验证客户端代码、测试Node-RED流程甚至给同事做一个演示Demo。等真机到位了只需要把数据源从模拟变量替换成从PLC驱动读取其他逻辑基本不用动。3. 写一个真正的客户端去读写数据3.1 客户端初始化与连接服务端起来之后客户端就好办了。客户端代码的核心是先找到服务端的端点然后创建会话最后通过会话读写节点。这里我仍然用C#因为跟服务端的代码风格一致放一起看比较顺。using Opc.Ua; using Opc.Ua.Configuration; var config await ApplicationInstance.LoadApplicationConfiguration(client.config.xml, silent: false); // 选择端点这里指定使用无安全策略的连接方便排查问题 var endpointDescription CoreClientUtils.SelectEndpoint( config, opc.tcp://localhost:4840, useSecurity: false); var endpointConfiguration EndpointConfiguration.Create(config); var endpoint new ConfiguredEndpoint(null, endpointDescription, endpointConfiguration); var session await Session.Create( config, endpoint, false, my-client-session, 60000, new UserIdentity(new AnonymousIdentityToken()), null); Console.WriteLine(Session created: session.NodeId);Session.Create里的60000是会话超时时间我调得比较大主要是在局域网内调试时避免因为偶发延迟导致会话断开。生产环境里这个值要根据网络质量来定太短容易频繁掉线太长又会让服务端资源被无效占用。3.2 节点寻址ns...;s...到底是什么意思创建完会话下一个绕不开的问题是读哪个节点。OPC UA里节点用NodeId来标识最常用的两种写法是ns2;i85和ns2;sTemperature。这里ns是命名空间索引i是数字标识符s是字符串标识符。为什么要命名空间因为不同厂商的节点可能存在同一个地址空间里命名空间是为了隔离标识符的归属避免冲突。比如A厂商定义的“温度”是ns2;sTemperatureB厂商定义的可能是ns3;sTemperature互不干扰。你如果不清楚目标节点的命名空间和标识符可以在服务端代码里事先定义好也可以用后面说的UA Expert去浏览节点树。说个我踩过的坑早期我直接把NodeId写成sTemperature忘了带上命名空间结果服务端一直返回BadNodeIdUnknown。后来养成一个习惯任何节点ID都写ns...;...完整形式问题就少了很多。3.3 订阅实时变化如果只是读一次数据用ReadValue就够了。但很多场景是连续监控比如报警信号要实时响应这时候轮询读写虽然也能用但性能差且容易漏掉瞬时的状态变化。OPC UA的订阅机制就是为此设计的。订阅的思路是客户端订阅服务端上的一组节点MonitorItem然后设置一个发布间隔服务端定期把变化值推给客户端客户端再触发一个回调函数。代码结构大致是这样的var subscription new Subscription { PublishingInterval 1000, LifetimeCount 100, KeepAliveCount 10 }; session.AddSubscription(subscription); subscription.Create(); var monitoredItem new MonitoredItem { StartNodeId new NodeId(ns2;sTemperature, 2), SamplingInterval 500, QueueSize 10, DiscardOldest true }; monitoredItem.Notification (item, value) { var val value.GetValue(); Console.WriteLine($Temperature: {val}); }; subscription.AddItem(monitoredItem); subscription.ApplyChanges();这个机制让我特别喜欢OPC UA的地方就是它的订阅是面向会话的客户端断线重连之后会自动恢复。实测下来服务端有数据变化客户端大概几百毫秒就能收到实时性完全够用而且比轮询省带宽。4. 用Node-RED把OPC UA可视化4.1 从纯代码到图形化编程很多自动化项目里OPC UA服务端和客户端配好之后还需要一层业务逻辑数据展示、阈值判断、写入控制、转发到数据库或者消息队列。如果每次都写C#程序维护成本不小。Node-RED这种基于流的图形化编程工具就很适合做这层“胶水”工作。它有专门的OPC UA节点可以订阅节点变化也可以写值还自带仪表盘节点几分钟就能搭出一个监控界面。需要提醒的是Node-RED里的OPC UA节点有两个常用方向一个是作为客户端连别人的服务端node-red-contrib-opcua-client另一个是作为服务端把数据暴露给别人node-red-contrib-opcua-server。多数项目场景是用客户端模式所以我们主要会用到前者。4.2 读取OPC UA节点并展示我实际跑通的流程是这样的先在Node-RED里安装好node-red-contrib-opcua-client节点这个节点会有一堆子节点比如OPC UA Client、OPC UA Item、OPC UA Events等。启动一个OPC UA Client节点配置里填上服务端的地址比如opc.tcp://localhost:4840安全策略选None连接模式设成Client。接着拖一个OPC UA Item节点在配置里Browse服务端的节点树选到Temperature这个节点。这个节点会自动帮你生成NodeId不用手写。然后把OPC UA Item节点接到一个chart仪表盘节点上部署之后仪表盘上就开始实时显示温度曲线了。这块给我的体验是Node-RED很适合做原型验证。第一次接OPC UA的时候我用C#写了个客户端来回折腾了好几个小时而用Node-RED只要服务端是通的配置好节点十分钟就能看到实时数据曲线。做项目前期的演示和沟通非常有价值。4.3 写回控制节点读数据之外Node-RED还可以写值。比如需要在界面上按一个按钮把某个启动信号写入服务端节点这不需要写后端接口直接用OPC UA Item节点的写模式就能实现。在Node-RED里这个流程是仪表盘按钮节点 → 修改msg.payload → OPC UA Item节点模式设为Write→ 服务端收到新值。比较关键的一点是写值的时候要考虑数据类型的匹配。仪表盘按钮输出的可能是布尔值或字符串但服务端节点期望的是Int16或者Double如果类型不对写操作会返回BadTypeMismatch。这个问题排查起来不难用UA Expert看一下节点数据描述然后在Function节点里做一次类型转换就行。5. 实例化过程中我踩过的坑5.1 常见错误码与排查速查表写OPC UA实例程序调试初期最容易遇到的就是各种Bad开头的错误码。我把实际项目中碰到较多的几种整理成了表格方便你排查。错误码含义我遇到的场景解决思路BadNodeIdUnknown节点不存在NodeId写错或命名空间不对用UA Expert或服务端日志确认节点IDBadSecurityModeRejected安全模式不被接受客户端用了None服务端强制SignAndEncrypt两端安全策略必须匹配BadTypeMismatch数据类型不匹配写入时的类型跟节点定义不一致确认节点的DataType转换后再写BadSessionIdInvalid会话无效会话超时或客户端断开未重连检查超时时间实现自动重连逻辑BadCertificateUntrusted证书不受信任客户端没有信任服务端的证书把两端的证书加入信任列表BadTimeout操作超时服务端节点多、查询节点树时网络卡顿增大请求超时时间分批浏览这张表不值得背下来但它有参考价值。我的方法是遇到错误码先去翻一下SDK源码或官方文档里对应的定义然后判断是配置问题、网络问题、还是代码逻辑问题。大多数时候错在配置层面的比例比较高尤其是证书和安全策略。5.2 证书问题的正确处理姿势证书问题是OPC UA新手最容易绕弯子的地方。第一次跑通实例程序时我图方便把安全策略设成None但项目上线前总归要启用加密所以提前把证书流程摸透是有价值的。OPC UA的安全机制是双向证书认证服务端有应用证书客户端也有应用证书两边需要互相建立信任通信才会被允许。实际部署中我习惯先把服务端的证书导出来复制到客户端的pki/trusted/certs目录下再把客户端的证书复制到服务端对应目录。SDK通常都提供命令行工具或者自动生成证书的机制在这个基础上把证书加入受信任列表连接就通了。踩坑提示不少人遇到证书问题后第一反应是直接把所有安全策略关掉纯匿名访问。这在测试环境可以生产环境我不建议。后来我排查过一些设备被误操作的问题很多就是因为没有启用安全机制任何人都能连上服务端写数据。所以安全策略和证书流程虽然麻烦但该上的还是得按时上。5.3 调试利器UA Expert这个工具我强烈推荐。UA Expert是OPC基金会官方的通用OPC UA客户端不用写代码填一个地址就能连接服务端浏览节点树、读写值、看订阅。我在开发服务端时基本离不开它。UA Expert有一个非常实用的功能Browse节点树的时候它会直接显示每个节点的属性包括NodeId、DataType、AccessLevel等。这意味着你可以直接从一个真实服务端里拷出标准的NodeId填到自己的客户端或Node-RED配置里省去了很多猜测。实例程序中如果遇到“连接倒是成功了但读不到数据”这类问题我一般先用UA Expert连一次能连上说明服务端正常问题多半出在自己的客户端配置上。根据我的实际体会OPC UA实例程序最大的门槛不是写代码而是理解它的分层模型和调试方法。协议规范本身很庞大但做数据采集和读写掌握服务端/客户端的创建、节点寻址、订阅机制、证书配置这几个核心点就能覆盖大部分场景。先把最小实例跑通再逐步加功能这条路走下来基本不会乱了阵脚。如果以后有机会我再单独聊聊OPC UA的历史数据查询和报警事件处理那又是另一片天地了。本文还有配套的精品资源点击获取