Read / Write
前置阅读
- 数据类型 OpcUaVariant 和 OpcUaDataValue 是所有读写的基础.
- 高频访问相同节点请用 registerNodes 加速.
- 实时变化通知用 Subscription 而不是循环 read.
read
public OpcUaDataValue read(String nodeId);
public OpcUaDataValue read(String nodeId, Enums.AttributeId attribute);
读单节点的指定 Attribute.
| 参数 | 类型 | 默认 | 说明 |
|---|---|---|---|
nodeId | String | — | NodeId 字符串 (i=2258 / ns=2;s=...) |
attribute | Enums.AttributeId | Value (13) | 要读的属性 |
返回: OpcUaDataValue (实现 AutoCloseable, 必须 close)
异常: OpcUaException (BadNodeIdUnknown / BadNotReadable / BadCommunicationError / ...)
// 读 Value
try (OpcUaDataValue dv = ua.read("ns=2;s=Temperature")) {
System.out.println(dv.variant().asDouble());
}
// 读 DisplayName
try (OpcUaDataValue dn = ua.read("ns=2;s=Temperature", Enums.AttributeId.DisplayName)) {
System.out.println(dn.variant().asString());
}
readMany
一次 RPC 批量读多个节点的同一 Attribute.
public List<OpcUaDataValue> readMany(List<String> nodeIds, Enums.AttributeId attribute);
List<String> nodes = Arrays.asList("ns=2;s=T1", "ns=2;s=T2", "ns=2;s=T3");
List<OpcUaDataValue> values = ua.readMany(nodes, Enums.AttributeId.Value);
try {
for (int i = 0; i < nodes.size(); i++) {
OpcUaDataValue dv = values.get(i);
if (dv != null) {
System.out.println(nodes.get(i) + " = " + dv.variant());
}
}
} finally {
for (OpcUaDataValue dv : values) if (dv != null) dv.close();
}
性能: 批量比循环单读快 ~N 倍 (省 N-1 次 RPC 往返).
必须逐项 close
readMany 返回的列表里每个 OpcUaDataValue 都持有 native handle. try-finally 逐项 close, 否则泄漏.
write
public Enums.StatusCode write(String nodeId, OpcUaVariant value);
public Enums.StatusCode write(String nodeId, OpcUaVariant value, Enums.AttributeId attribute);
写单节点.
try (OpcUaVariant v = new OpcUaVariant().setDouble(42.5)) {
Enums.StatusCode st = ua.write("ns=2;s=Setpoint", v);
if (st != Enums.StatusCode.Good) {
System.err.println("Write failed: " + st);
}
}
返回 Enums.StatusCode (Good / BadNotWritable / BadTypeMismatch / BadUserAccessDenied / ...).
业务级失败不抛异常
write 不抛异常 (除非 transport 级错误), 业务级失败通过返回值判断. 必须检查返回值, 不要假定 Good.
OpcUaVariant 构造速查
new OpcUaVariant().setBoolean(true) // Boolean
new OpcUaVariant().setSByte((byte)-1) // SByte
new OpcUaVariant().setByte(255) // Byte
new OpcUaVariant().setInt16((short)42) // Int16
new OpcUaVariant().setUInt16(42) // UInt16
new OpcUaVariant().setInt32(42) // Int32
new OpcUaVariant().setUInt32(42) // UInt32
new OpcUaVariant().setInt64(42L) // Int64
new OpcUaVariant().setFloat(3.14f) // Float
new OpcUaVariant().setDouble(3.14) // Double
new OpcUaVariant().setString("hello") // String
new OpcUaVariant().setDateTime(fileTimeLong) // DateTime (Windows FileTime)
OpcUaDataValue 字段速查
| 方法 | 返回 | 说明 |
|---|---|---|
variant() | OpcUaVariant | 实际值 (不持有, 不要 close) |
getStatusEnum() | Enums.StatusCode | Good / Bad* / Uncertain* |
isGood() | boolean | 等价 getStatusEnum().isGood() |
sourceTimestamp() | long | 数据源 FileTime, 0 = null |
serverTimestamp() | long | 服务端 FileTime, 0 = null |
try (OpcUaDataValue dv = ua.read("ns=2;s=T1")) {
if (!dv.isGood()) {
System.err.println("Bad value: " + dv.getStatusEnum());
return;
}
Double v = dv.variant().asDouble();
long ft = dv.sourceTimestamp();
Instant t = (ft > 0)
? Instant.ofEpochMilli((ft - 116444736000000000L) / 10000L)
: Instant.now();
System.out.println(v + " @ " + t);
}
AttributeId 常用值
| Id | 名称 | 说明 |
|---|---|---|
| 1 | NodeId | 节点 ID |
| 2 | NodeClass | 节点类别 |
| 3 | BrowseName | 浏览名 |
| 4 | DisplayName | 显示名 |
| 5 | Description | 描述 |
| 13 | Value | 变量值 (默认) |
| 14 | DataType | 数据类型 NodeId |
| 17 | AccessLevel | 访问权限位 |
| 20 | Historizing | 是否在记录历史 |
| 21 | Executable | Method 是否可执行 |
最佳实践
- 批量优先: 监控 100+ Tag 用
readMany, 不要循环read(省 N-1 次 RPC) - 必须 close:
OpcUaDataValue实现 AutoCloseable, 漏 close 会泄漏 native 内存 - write 检查返回值: 不要假定一定 Good
- 实时变化用订阅: 不要 1 秒一次轮询 read, 改用
SubscriptiononDataChanged (服务端推送) - OpcUaVariant 也要 close: 自己
new OpcUaVariant()构造的, 用try-with-resources包起来