OPC UA
Zeus.Protocols.OpcUa 面向 PLC、SCADA、网关和现场 Server 的 opc.tcp 接入。当前支持 SecurityPolicy None、匿名或用户名密码登录、会话、批量 Value 读写、NodeId 点表采集、按点名写回、JSON 配置和虚拟 Server 联调。
证书加密、订阅/MonitoredItem、Browse 和复杂 ExtensionObject 不在本包范围内。现场若必须使用 SignAndEncrypt,请先在 Server 上开放 None 策略,或单独沟通企业适配。
安装
dotnet add package Zeus.Communications
dotnet add package Zeus.Protocols.OpcUa
OPC UA 通常使用 TCP 4840 端口。通道仍只负责收发字节,HEL/ACK、安全通道、会话和 NodeId 编解码由 OpcUaClient 处理。
无硬件联调
先用内存 Server 验证点表和写回:
using Zeus;
var memory = new OpcUaServerMemory();
memory.SetInt32("ns=2;s=RawTemperature", 253, writable: true);
memory.SetText("ns=2;s=Location", "line-a", writable: true);
await using var app = ZeusHost.Create(builder =>
{
builder.AddAcquisition(TimeSpan.FromMilliseconds(500));
builder.AddVirtualChannel("opcua-link", new OpcUaServerResponder(memory));
builder.AddOpcUa(
"plc",
"opcua-link",
points: map => map
.Int32("temperature", "ns=2;s=RawTemperature", scale: 0.1)
.Writable("temperature")
.String("location", "ns=2;s=Location"));
});
await app.StartAsync();
Console.WriteLine(app.Points.Get<double>("temperature"));
await app.Points.WriteAsync("temperature", 18.6);
OpcUaServerMemory 预置了可写的 ns=2;s=ServerName 和只读的 i=2258(ServerStatus.CurrentTime),也可以通过 Set、SetDouble、SetText 等方法添加现场节点。
连接真实设备
把虚拟通道替换成 TCP 客户端:
await using var app = ZeusHost.Create(builder =>
{
builder.AddTcpClient("opcua-link", "192.168.1.20", 4840);
builder.AddOpcUa(
"plc",
"opcua-link",
new OpcUaOptions
{
EndpointUrl = "opc.tcp://192.168.1.20:4840",
SessionName = "zeus-hmi",
Anonymous = true
},
points: map => map
.Double("temperature", "ns=2;s=Temperature")
.Boolean("running", "ns=2;s=Running"));
});
await app.StartAsync();
EndpointUrl 需与远端 Server 暴露的 endpoint 一致;不少现场 Server 会校验 HEL/CreateSession 中的 URL。设备若要求用户名密码,设置 Anonymous = false 并填写 Username / Password。Server 必须允许 SecurityPolicy None。
直接读写
var plc = app.Devices.Get<OpcUaDevice>("plc");
var temperature = await plc.Client.ReadAsync("ns=2;s=Temperature");
var name = await plc.Client.ReadTextAsync("ns=2;s=ServerName");
await plc.Client.WriteAsync("ns=2;s=Temperature", OpcUaValue.Double(18.6));
OpcUaClient 会串行化同一通道上的请求,并在首次读写前自动完成 HEL、OpenSecureChannel、CreateSession 和 ActivateSession。
点表类型
| API | OPC UA 类型 | Zeus 值 |
|---|---|---|
Boolean | Boolean | bool |
Int32 / UInt32 / Int64 | Int32 / UInt32 / Int64 | 对应整数,可选 scale 后为 double |
Float / Double | Float / Double | double |
String | String | UTF-8 string |
DateTime | DateTime | UTC DateTime |
ByteString | ByteString | byte[] |
Typed | 上表任一内置标量 | 按指定类型装箱 |
数值点支持报警限。写回时 scale 会反向换算,例如采集值 25.3、scale: 0.1 会向 Server 写入原始值 253。
NodeId 使用规范文本:i=2258、ns=2;s=Temperature、ns=1;g=...、ns=1;b=Base64。
JSON 配置
{
"acquisition": {
"intervalMilliseconds": 500,
"pollImmediately": true
},
"channels": [
{
"name": "opcua-link",
"type": "virtual",
"options": {
"responder": "opcua"
}
}
],
"devices": [
{
"name": "plc",
"channel": "opcua-link",
"type": "opcua",
"options": {
"opcUaSessionName": "zeus-hmi"
},
"points": [
{
"name": "serverName",
"options": {
"nodeId": "ns=2;s=ServerName",
"dataType": "string",
"writable": true
}
},
{
"name": "temperature",
"options": {
"nodeId": "ns=2;s=RawTemperature",
"dataType": "int32",
"scale": 0.1,
"lowAlarmLimit": 0,
"highAlarmLimit": 80
}
}
]
}
]
}
设备类型支持 opcua、opc-ua 和 opc.ua。虚拟通道的 responder 使用 opcua;真实设备则把通道改为 tcp,填写设备 IP 和 4840 端口。
排查
| 现象 | 首先检查 |
|---|---|
| HEL/ACK 超时 | 设备 IP、TCP 4840、防火墙、Server 是否开启 |
| SecurityPolicy 被拒绝 | Server 未开放 None,或要求证书加密 |
| BadIdentityTokenInvalid | 用户名密码或匿名策略不匹配 |
| BadNodeIdUnknown | NodeId 文本、命名空间索引或标识符与地址空间不一致 |
| BadTypeMismatch | dataType 与节点实际内置类型不一致 |
| 点表没有值 | NodeId、数据类型、首次采集时间和点表错误快照 |
现场排查时打开通道报文追踪,确认 HEL、OPN、MSG 是否成对出现,以及 RequestId 是否匹配。