跳到主要内容

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_connectedvoid(std::string const& url)首次连接成功
on_disconnectedvoid(std::string const& url, Status reason)主动 / 被动断开
on_reconnectingvoid(std::string const& url)检测到断线, 启动自动重连
on_reconnectedvoid(std::string const& url)自动重连成功
on_keepalivevoid(int64_t server_time_ft, Status server_status)KeepAlive 心跳成功
on_state_changedvoid(SessionState old, SessionState new, Status reason)任何 SessionState 变化
on_communication_errorvoid(Status code, std::string const& cat, std::string const& msg)通讯失败 (KeepAlive 失败 / Read 异常等)
on_anyvoid(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触发场景
OnNodeRemovedNodeRemoved命中 BadNodeIdUnknown
OnNodeAccessDeniedNodeAccessDenied命中 BadUserAccessDenied
OnNodeTypeChangedNodeTypeChangedWrite 时命中 BadTypeMismatch
OnSessionLostSessionLostSession 失效预警
OnServerStateChangedServerStateChanged服务端状态切换
OnSubscriptionRestoredSubscriptionRestored订阅恢复成功

每个 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 看具体异常.

下一步