连不上PLC主因是OPC UA服务未启、4840端口被防火墙拦截或证书不被信任;需用UaExpert验证连接,C#中显式处理证书验证、动态获取Endpoint、正确构造NodeId、批量读写并注意厂商命名空间差异。

用 OPCFoundation.NetStandard.OpcUa 连不上 PLC?先确认端口和证书
绝大多数连不上不是代码问题,而是 OPC UA 服务没跑起来、防火墙拦了 4840 端口,或客户端证书被拒绝。PLC(比如西门子 S7-1500、罗克韦尔 ControlLogix)默认不自动信任你的开发机证书,首次连接会静默失败。
实操建议:
- 用
UaExpert(免费)先连一次目标 PLC 的 OPC UA endpoint(如opc.tcp://192.168.0.1:4840),看是否弹出证书信任提示——如果弹了,说明服务端正常,只是你 C# 客户端没处理证书验证逻辑 - 在 C# 中必须显式配置
ApplicationConfiguration.CertificateValidation += (s, e) => e.Accept = true;(仅测试环境!生产需校验指纹) - 检查 PLC 是否启用了 OPC UA Server 功能(S7-1500 需在 TIA Portal 中勾选“启用 OPC UA 服务器”,并分配访问权限)
Session.Create 报错 “BadNotConnected” 或 “BadTimeout” 怎么办
这不是网络不通的泛泛提示,而是 SDK 在尝试建立安全通道和会话时卡在某个环节。常见于未正确设置 EndpointDescription 或服务端要求特定安全策略。
实操建议:
- 别直接 new
EndpointDescription,用ConfiguredEndpoint+DiscoveryClient.GetEndpoints动态获取真实 endpoint 列表,从中挑一个SecurityPolicyUri匹配的(比如http://opcfoundation.org/UA/SecurityPolicy#Basic256Sha256) - 确保
UserIdentity类型匹配:PLC 通常只支持匿名(new AnonymousIdentity())或用户名密码(new UserTokenPolicy(UserTokenType.UserName)),不支持证书登录 - 超时值别设太短——
session = await session.CreateAsync(..., 30000)至少留 30 秒,握手过程比 HTTP 慢得多
读写 NodeId 标签总返回空或 BadNodeIdUnknown
PLC 的标签路径不是直接写变量名,而是要按地址空间层级拼出标准 NodeId。写错格式(比如漏掉命名空间索引、误用字符串编码)会导致服务端根本找不到节点。
实操建议:
- 先用 UaExpert 展开地址空间,右键节点 → “Copy NodeId” → 粘贴出来看真实格式,例如:
i=85(对象)、s=Channel1.Device1.Pressure(字符串形式)、ns=3;s=::AsGlobalPV:TempSet(西门子常用) - C# 中优先用
NodeId.Parse("ns=3;s=::AsGlobalPV:TempSet"),别手拼new NodeId("TempSet", 3)——后者只适用于简单标识符,PLC 多用带冒号的路径 - 写操作前务必先
ReadValue一次,确认节点可读;有些 PLC 节点默认禁写,需在 TIA Portal 或 Logix Designer 中单独勾选“允许 OPC UA 写入”
批量读写多个标签性能差,CPU 占用高
逐个调用 ReadRequest 或 WriteRequest 是最慢的方式,每次请求都走完整协议栈,网络往返放大延迟。OPC UA 原生支持批量,但 SDK 默认不帮你合并。
实操建议:
- 把要读的
NodeId全部塞进一个ReadRequest的NodesToRead列表,一次最多 100 个(PLC 实际支持数取决于固件,S7-1500 v2.9+ 支持 50~200) - 避免在循环里反复 new
ServiceResult或DataValue——复用ReadResponse对象,解析响应时直接索引Results数组 - 不要用
await串行读 10 个点,改用Task.WhenAll并发多个ReadRequest(但并发数别超 3,否则 PLC 可能丢包或降级响应)
真正麻烦的不是语法,是每个 PLC 厂商对 OPC UA 的实现细节差异——西门子用 ns=3,罗克韦尔用 ns=2,倍福甚至要用 ns=4;s=MainVarGroup.MyVar。调试时别只盯自己代码,先抓包看实际交换的二进制请求里 NodeId 长什么样。


















