事件订阅 (C)
OPC UA 事件 (Alarms & Conditions) 通过 DarraUa_Subscription_AddEventItem 订阅. 这是 OPC UA 协议事件, 与 SDK 的 DataChange 回调 不是一回事.
区别
DarraUa_Subscription_AddEventItem— OPC UA 协议事件 (报警 / 条件, 服务端推送, 本节)DarraUa_OnDataChange— MI 数据变化 (变量值变了)DarraUa_OnSubscriptionState— SDK 内部订阅状态 (与协议无关)
事件 API 在独立头文件:
#include <darra_opcua/darra_opcua_event.h>
API
DARRA_OPCUA_API DarraUa_Status DARRA_OPCUA_CALL DarraUa_Subscription_AddEventItem(
DarraUa_SubscriptionHandle sub,
const char* object_node_id_str, /* NULL = "i=2253" Server */
DarraUa_OnEvent cb,
void* user_context,
DarraUa_MonitoredItemHandle* out_mi);
| 参数 | 默认 | 说明 |
|---|---|---|
object_node_id_str | NULL → i=2253 (Server) | 事件源节点, 必须有 EventNotifier Attribute (12) |
cb | — | 事件到达回调 |
out_mi | — | 出参, 新 MI 句柄 |
Event 载荷
typedef struct {
uint16_t severity; /* 1-1000 */
const char* message; /* LocalizedText.text, UTF-8 */
const char* source_name; /* UTF-8 */
DarraUa_DateTime time; /* UTC 100ns since 1601 */
const char* event_type; /* NodeId 字符串, 可能 NULL */
} DarraUa_Event;
| 字段 | 含义 |
|---|---|
severity | 1-1000, 数值越大越严重 |
message | 人类可读消息 |
source_name | 触发事件的源对象名称 |
time | 事件发生时间戳 (服务端) |
event_type | 事件类型 NodeId, 例如 "i=2058" (BaseEventType) |
生命周期: ev 指针仅回调内部有效, 字符串字段同理. 如需保留请 strdup 或拷贝到自己的 buffer.
Severity 等级
| 范围 | 标签 |
|---|---|
| 1-249 | Low |
| 250-499 | Medium |
| 500-749 | High |
| 750-1000 | HighHigh |
事件回调签名
typedef void (*DarraUa_OnEvent)(
DarraUa_SubscriptionHandle sub,
DarraUa_MonitoredItemHandle mi,
const DarraUa_Event* ev,
void* user_context);
内置 EventFilter
SDK 固定使用 4 个 SimpleAttributeOperand (BaseEventType 标准字段):
| 索引 | 字段 |
|---|---|
| 0 | Severity (UInt16) |
| 1 | Message (LocalizedText) |
| 2 | SourceName (String) |
| 3 | Time (DateTime) |
whereClause = 空, 即所有事件均上报. 适用绝大部分 Server. 自定义 EventFilter 在后续 SDK 版本提供.
完整示例
#include <darra_opcua/darra_opcua.h>
#include <darra_opcua/darra_opcua_event.h>
#include <stdio.h>
#if defined(_WIN32)
# include <windows.h>
# define OPCUA_SLEEP_MS(ms) Sleep(ms)
#else
# include <unistd.h>
# define OPCUA_SLEEP_MS(ms) usleep((ms) * 1000)
#endif
static int g_event_count = 0;
static const char* severity_label(uint16_t s)
{
if (s == 0) return "None";
if (s < 250) return "Low";
if (s < 500) return "Medium";
if (s < 750) return "High";
return "HighHigh";
}
static void on_event(DarraUa_SubscriptionHandle sub,
DarraUa_MonitoredItemHandle mi,
const DarraUa_Event* ev, void* ctx)
{
(void)sub; (void)mi; (void)ctx;
if (!ev) return;
g_event_count++;
printf("[event #%d] sev=%u (%s) src=\"%s\" type=\"%s\" time=%lld msg=\"%s\"\n",
g_event_count,
(unsigned)ev->severity, severity_label(ev->severity),
ev->source_name ? ev->source_name : "",
ev->event_type ? ev->event_type : "",
(long long)ev->time,
ev->message ? ev->message : "");
}
int main(int argc, char** argv)
{
const char* endpoint = (argc >= 2) ? argv[1] : "opc.tcp://localhost:4840";
DarraUa_Initialize();
DarraUa_SessionConfig cfg;
DarraUa_SessionConfig_Init(&cfg);
cfg.endpoint_url = endpoint;
cfg.security_policy_uri = "http://opcfoundation.org/UA/SecurityPolicy#None";
DarraUa_SessionHandle h = DARRA_UA_INVALID_SESSION_HANDLE;
DarraUa_Session_Create(&cfg, &h);
DarraUa_Session_Connect(h);
DarraUa_SubscriptionConfig sc;
DarraUa_SubscriptionConfig_Init(&sc);
sc.publishing_interval_ms = 500.0;
DarraUa_SubscriptionHandle sub = DARRA_UA_INVALID_SUBSCRIPTION_HANDLE;
DarraUa_Subscription_Create(h, &sc, &sub);
/* 在 Server (i=2253) 上加 Event MI */
DarraUa_MonitoredItemHandle ev_mi = DARRA_UA_INVALID_MONITORED_ITEM_HANDLE;
DarraUa_Status st = DarraUa_Subscription_AddEventItem(
sub, "i=2253", on_event, NULL, &ev_mi);
if (!DARRA_UA_STATUS_IS_GOOD(st)) {
fprintf(stderr, "AddEventItem failed: %s\n", DarraUa_StatusName(st));
}
/* Pump 10 秒 */
for (int i = 0; i < 20; ++i) {
OPCUA_SLEEP_MS(500);
DarraUa_Session_Publish(h, 2000);
}
printf("Total events received: %d\n", g_event_count);
DarraUa_Subscription_Delete(sub);
DarraUa_Session_Disconnect(h);
DarraUa_Session_Close(h);
DarraUa_Shutdown();
return 0;
}
复用已有 Subscription
事件 MI 可挂在现有数据 Subscription 上, 共享 publishingInterval:
DarraUa_SubscriptionHandle sub = /* 已有数据订阅 */;
DarraUa_Subscription_AddNode(sub, "ns=2;s=Counter", DARRA_UA_ATTR_VALUE,
-1.0, on_change, NULL, NULL); /* 数据 MI */
DarraUa_Subscription_AddEventItem(sub, "i=2253", on_event, NULL, NULL); /* 事件 MI */
EventNotifier
只有 EventNotifier Attribute (12) 非 0 的节点才能产生事件. 默认 Server (i=2253) 是 EventNotifier. 自定义节点是否有事件源能力, 看服务端实现.
报警操作 (Acknowledge / Confirm)
收到 AlarmConditionType 事件后, 操作员通常要 Acknowledge (确认). 通过 DarraUa_Session_Call 调用报警节点的 Acknowledge Method:
/* condition_id 是触发报警的条件节点 NodeId */
DarraUa_NodeId obj_id, mtd_id;
/* 解析 condition_id 与 condition_id + ".Acknowledge" 到 NodeId 结构, 略 */
DarraUa_Variant* args[2] = { /* eventId */, /* comment */ };
DarraUa_Variant* outs = NULL;
uint32_t out_n = 0;
DarraUa_Status method_st = 0;
DarraUa_Session_Call(h, &obj_id, &mtd_id,
(const DarraUa_Variant* const*)args, 2, &outs, &out_n, &method_st);
字符串保存模板
回调内字段生命周期短, 想保留事件:
typedef struct {
uint16_t severity;
char* message;
char* source_name;
DarraUa_DateTime time;
} SavedEvent;
static SavedEvent* g_events = NULL;
static int g_n = 0, g_cap = 0;
static char* dup_or_null(const char* s)
{
if (!s) return NULL;
char* out = (char*)malloc(strlen(s) + 1);
if (out) strcpy(out, s);
return out;
}
static void on_event_save(DarraUa_SubscriptionHandle s, DarraUa_MonitoredItemHandle m,
const DarraUa_Event* ev, void* ctx)
{
(void)s; (void)m; (void)ctx;
if (!ev) return;
if (g_n == g_cap) {
g_cap = g_cap ? g_cap * 2 : 16;
g_events = (SavedEvent*)realloc(g_events, g_cap * sizeof(SavedEvent));
}
SavedEvent* dst = &g_events[g_n++];
dst->severity = ev->severity;
dst->message = dup_or_null(ev->message);
dst->source_name = dup_or_null(ev->source_name);
dst->time = ev->time;
}