数据回调
Subscription::AddNode / AddMany 接受 std::function<void(DataValue const&)> 回调, 在 MI 数据变化时由服务端推送触发.
using OnDataChange = std::function<void(DataValue const&)>;
关联事件
- 全局会话事件统一通道见
s.Events().*. - OPC UA 报警 / 条件事件 (协议事件) 见 事件订阅.
DataValue 字段访问
回调里的 DataValue 是借用, 生命周期仅在回调内. 直接访问字段:
| 访问器 | 类型 | 说明 |
|---|---|---|
dv.Value() | Variant | 实际值 (借用) |
dv.StatusCode() | uint32_t | 原始 32-bit 状态码 |
dv.StatusEnum() | Status | 强类型枚举 |
dv.IsGood() | bool | 等价 StatusCode() == 0 |
dv.SourceTimestamp() | int64_t | FileTime |
dv.ServerTimestamp() | int64_t | FileTime |
例子
简单日志
sub.AddNode("ns=2;s=T1", [](DataValue const& dv) {
std::cout << "ft=" << dv.SourceTimestamp()
<< " val=" << dv.Value().AsString()
<< " status=0x" << std::hex << dv.StatusCode() << "\n";
});
写文件
std::mutex write_lock;
sub.AddNode("ns=2;s=T1", [&](DataValue const& dv) {
std::lock_guard<std::mutex> lock(write_lock);
std::ofstream out("data.csv", std::ios::app);
out << dv.SourceTimestamp() << ","
<< dv.Value().AsString() << "\n";
});
UI 绑定 (Qt)
sub.AddNode("ns=2;s=T1", [&](DataValue const& dv) {
// 切回 UI 线程 (Qt)
QMetaObject::invokeMethod(qApp, [&, val = dv.Value().AsString()]() {
labelTemp->setText(QString::fromStdString(val));
});
});
必须切回 UI 线程
否则跨线程 UI 操作崩溃.
线程模型
- 回调在 C 层 Publish 线程执行, 单线程串行 (同一订阅内部不会并发)
- 回调里不要执行长操作 (>100 ms), 否则会阻塞 Publish 队列
- 回调里抛异常会被 SDK 内部 try-catch 吃掉 (避免 native 崩溃), 不会传播
- 跨订阅的回调在不同 Publish 线程, 可能并发 — 共享数据自己加锁
DataValue 借用注意
DataValue 在回调结束后失效, 不要保存指针 / 引用:
DataValue* g_last = nullptr; // ❌ 不要这样
sub.AddNode("ns=2;s=T1", [&](DataValue const& dv) {
g_last = const_cast<DataValue*>(&dv); // ❌ use-after-free
});
如果需要把数据传出回调, 同步抽取值:
struct Snapshot { int64_t ft; double val; uint32_t status; };
std::atomic<Snapshot> latest;
sub.AddNode("ns=2;s=T1", [&](DataValue const& dv) {
Snapshot s;
s.ft = dv.SourceTimestamp();
s.status = dv.StatusCode();
dv.Value().TryGetDouble(s.val);
latest.store(s);
});
与 s.Events() 的关系
// 方式 A: 单订阅级 (本节)
sub.AddNode("ns=2;s=T1", [](DataValue const&) { /* ... */ });
// 方式 B: 全局统一通道 (所有订阅汇集)
s.Events().on_any = [](OpcUaEventEntry const& e) {
if (e.category == OpcUaEventCategory::DataChange) { /* ... */ }
};
两个事件对同一数据点都会触发, 选一个用即可.