OpcUaDataValue
OpcUaDataValue 包装一次 read 返回的全部信息: 值 + 状态 + 时间戳.
实现 AutoCloseable — 持有 native handle, 必须 close.
配套
- 内部值容器请看 OpcUaVariant.
- 标准节点常量请看 WellKnownNodes.
公共方法
| 类别 | 属性 | 类型 | 访问 | 说明 |
|---|---|---|---|---|
| 值 | variant() | OpcUaVariant | R | 实际值 (不持有, 不要 close) |
getStatusEnum() | Enums.StatusCode | R | 数据质量 (枚举形式) Good / Bad* / Uncertain* | |
getStatusCode() | int | R | 原始 32-bit StatusCode | |
isGood() | boolean | R | 等价 (code & 0xC0000000) == 0 | |
| 时间戳 | sourceTimestamp() | long | R | 数据源 FileTime (100ns since 1601 UTC), 0 = null |
serverTimestamp() | long | R | 服务端 FileTime, 0 = null | |
| 内部 | handle() | Pointer | R | 内部 native handle (调试用) |
| 释放 | close() | void | — | 释放 native 内存 (owns=true 时) |
用法
try (OpcUaDataValue dv = ua.read("ns=2;s=Temperature")) {
// 检查质量
if (!dv.isGood()) {
System.err.println("Bad value: " + dv.getStatusEnum());
return;
}
// 取值
OpcUaVariant v = dv.variant();
Double temp = v.asDouble();
if (temp == null) {
System.err.println("Type mismatch");
return;
}
// 取时间戳
long ft = dv.sourceTimestamp();
Instant t = (ft > 0)
? Instant.ofEpochMilli((ft - 116444736000000000L) / 10000L)
: Instant.now();
System.out.printf("%s: %.2f °C%n", t, temp);
}
StatusCode 重点值
| 值 | 含义 |
|---|---|
Enums.StatusCode.Good (0x00000000) | 一切正常 |
Enums.StatusCode.Uncertain (0x40000000) | 不确定但可用 (例如传感器超量程) |
Enums.StatusCode.Bad (0x80000000) | 数据无效 |
Enums.StatusCode.BadCommunicationError | 通讯失败 |
Enums.StatusCode.BadNodeIdUnknown | 节点不存在 |
Enums.StatusCode.BadNotReadable | 该 Attribute 不可读 |
业务代码必须检查 getStatusEnum() / isGood(), 不能假设 Good.
Enums.StatusCode 提供 isGood() / isUncertain() / isBad() 按位判断方法.
时间戳含义
sourceTimestamp()— 数据真实产生时间 (传感器采样时刻 / 计算时刻). 历史 / 趋势 / 同步用这个serverTimestamp()— 服务端收到 / 处理时间. 用于诊断网络延迟. 一般不用于业务
如果服务端没设 SourceTimestamp, 它就是 ServerTimestamp 的副本.
返回 0 表示该时间戳为 null.
FileTime ↔ Instant 转换
import java.time.Instant;
// FileTime → Instant
long ft = dv.sourceTimestamp();
Instant t = (ft > 0)
? Instant.ofEpochMilli((ft - 116444736000000000L) / 10000L)
: Instant.now();
// Instant → FileTime
long ft2 = t.toEpochMilli() * 10000L + 116444736000000000L;
内存所有权
OpcUaDataValue 持有 native handle, 实现 AutoCloseable:
| 来源 | owns | 是否要 close |
|---|---|---|
ua.read(...) 返回 | true | 是 |
ua.readMany(...) 返回的每个元素 | true | 是 |
ua.readHistoryXxx(...) 返回的每个元素 | true | 是 |
node.getValue() 返回 | true | 是 |
漏 close 后果
漏 close 会泄漏 native 内存 (定时跑 100k+ 次会涨内存). 永远用 try-with-resources 或 try-finally 包.
最佳实践
// 单个
try (OpcUaDataValue dv = ua.read("ns=2;s=T1")) {
System.out.println(dv.variant());
}
// 批量
List<OpcUaDataValue> dvs = ua.readMany(nodes, Enums.AttributeId.Value);
try {
for (OpcUaDataValue dv : dvs) {
if (dv != null) System.out.println(dv.variant());
}
} finally {
for (OpcUaDataValue dv : dvs) if (dv != null) dv.close();
}
构造 (用于 HistoryUpdate)
通常由 SDK 内部构造. 用户用于 HistoryUpdate 的场景, 当前需要从已有 DataValue 派生 (后续版本会暴露公共构造器).