Events — s.Events().*
Session::Events() 返回 SessionEvents&, 是 SDK 内部事件统一通道, 把会话生命周期 / 心跳 / 通讯异常都汇集到一组 std::function 回调字段上, 方便上层 UI 做日志 / 状态栏 / 调试.
与 OPC UA 协议事件区分
s.Events().on_*— SDK 内部事件 (本节, 与协议无关)s.SubscribeEvents(...)— OPC UA 协议事件 (Alarms & Conditions), 见 事件订阅
SessionEvents 字段
字段式 std::function 回调, 直接赋值即可:
| 字段 | 签名 | 触发时机 |
|---|---|---|
| on_connected | void(std::string const& url) | 首次连接成功 |
| on_disconnected | void(std::string const& url, Status reason) | 主动 / 被动断开 |
| on_reconnecting | void(std::string const& url) | 检测到断线, 启动自动重连 |
| on_reconnected | void(std::string const& url) | 自动重连成功 |
| on_keepalive | void(int64_t server_time_ft, Status server_status) | KeepAlive 心跳成功 |
| on_state_changed | void(SessionState old, SessionState new, Status reason) | 任何 SessionState 变化 |
| on_communication_error | void(Status code, std::string const& cat, std::string const& msg) | 通讯失败 (KeepAlive 失败 / Read 异常等) |
| on_any | void(OpcUaEventEntry const&) | 统一事件通道 (全部事件汇集) |
OpcUaEventCategory 枚举
OpcUaEventEntry::category 字段, 完整清单:
enum class OpcUaEventCategory : int {
Unknown = 0,
Connected, Disconnected, Reconnecting, Reconnected,
KeepAlive, StateChanged,
SubscriptionCreated, SubscriptionLost, SubscriptionRestored,
MonitoredItemAdded, MonitoredItemRemoved, SubscriptionCleared,
DataChange, ServerEvent, Alarm,
Read, Write, Browse, Call, HistoryRead,
CommunicationError, SecurityError, ProtocolError,
Info, Diagnostic,
// 节点 / 会话级新增分类 (2026-04-25)
NodeRemoved = 30, // 服务端删除某节点 (BadNodeIdUnknown)
NodeAccessDenied = 31, // 当前用户对该节点权限不足
NodeTypeChanged = 32, // 节点 DataType 变了 (BadTypeMismatch)
SessionLost = 33, // Session 失效 (重连前预警)
ServerStateChanged = 34 // 服务端 Running → Failed/Shutdown 等
};
Typed 通道 (语法糖) — 6 个新事件
除了 on_any 统一通道, 6 个高频 Category 提供专属事件:
| 事件 | Category | 触发场景 |
|---|---|---|
| OnNodeRemoved | NodeRemoved | 命中 BadNodeIdUnknown |
| OnNodeAccessDenied | NodeAccessDenied | 命中 BadUserAccessDenied |
| OnNodeTypeChanged | NodeTypeChanged | Write 时命中 BadTypeMismatch |
| OnSessionLost | SessionLost | Session 失效预警 |
| OnServerStateChanged | ServerStateChanged | 服务端状态切换 |
| OnSubscriptionRestored | SubscriptionRestored | 订阅恢复成功 |
每个 typed 事件回调签名是 void(OpcUaEventEntry const&)。
ua.Events().OnNodeRemoved([](OpcUaEventEntry const& e) {
std::cout << "⚠ 节点已被服务端删除: " << e.source << "\n";
treeView->RemoveNode(e.source);
});
ua.Events().OnNodeAccessDenied([](OpcUaEventEntry const& e) {
std::cout << "⛔ 权限不足: " << e.source << "\n";
});
ua.Events().OnSessionLost([](OpcUaEventEntry const& e) {
statusBar->setText("⚠ 会话失效, 即将重连...");
});
ua.Events().OnServerStateChanged([](OpcUaEventEntry const& e) {
std::cout << "服务端状态: " << e.message << "\n";
});
ua.Events().OnSubscriptionRestored([](OpcUaEventEntry const& e) {
std::cout << "订阅已恢复: " << e.source << "\n";
});
OpcUaEventSeverity 枚举
enum class OpcUaEventSeverity : int {
Trace = 0, Debug = 1, Info = 2, Warn = 3, Error = 4, Fatal = 5
};
OpcUaEventEntry 结构
struct OpcUaEventEntry {
OpcUaEventCategory category = OpcUaEventCategory::Unknown;
OpcUaEventSeverity severity = OpcUaEventSeverity::Info;
std::string source;
std::string message;
Status status = Status::Good;
};
用法示例
通用日志钩子 (一行打印 SDK 内所有事件)
s.Events().on_any = [](OpcUaEventEntry const& e) {
std::cout << "[" << static_cast<int>(e.severity) << "] "
<< "[" << static_cast<int>(e.category) << "] "
<< e.source << ": " << e.message
<< " (0x" << std::hex << static_cast<uint32_t>(e.status) << ")\n";
};
调试期非常省事 — 不订阅具体事件, 一个 lambda 看全部.
状态栏绑定 (Qt 示例)
s.Events().on_state_changed = [&](SessionState old_s, SessionState new_s, Status reason) {
QMetaObject::invokeMethod(qApp, [&, new_s]() {
statusLabel->setText(QString("State=%1").arg(static_cast<int>(new_s)));
statusLabel->setStyleSheet(new_s == SessionState::Connected
? "color:green" : "color:red");
});
};
错误统计
std::atomic<int> error_count{0};
s.Events().on_communication_error = [&](Status, std::string const&, std::string const&) {
error_count.fetch_add(1);
};
KeepAlive 心跳输出服务端时间
constexpr int64_t WIN_EPOCH_OFFSET = 116444736000000000LL;
s.Events().on_keepalive = [](int64_t ft, Status status) {
time_t t = (ft - WIN_EPOCH_OFFSET) / 10000000;
std::cout << "KeepAlive @ " << t
<< " status=0x" << std::hex << static_cast<uint32_t>(status) << "\n";
};
线程模型
必须切回 UI 线程
回调在 C 层 Publish 线程或 KeepAlive 线程上调用, 不在调用方线程. UI 操作必须切回 UI 线程 (Qt QMetaObject::invokeMethod / WPF Dispatcher 等), 否则跨线程 UI 操作崩溃.
不要在事件里执行长操作 (>100 ms), 否则会阻塞 Publish 队列 / KeepAlive.
回调里的异常会被 SDK try-catch 吃掉 (避免 native 崩溃), 自己加 try-catch 看具体异常.