You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何通过node-opcua获取自定义OPC UA服务器事件的所有字段?

问题:无法获取自定义OPC UA服务器的事件字段

我有一段可正常运行的node-opcua事件订阅代码,在对接自定义OPC UA服务器时遇到问题:服务器会发送额外的自定义事件字段,但把这些字段名称添加到fields数组中时返回值为null;如果移除eventFilter配置,仅能接收到空事件。需要一个能获取事件所有可用字段的解决方案。

原代码:

import {
  AttributeIds,
  constructEventFilter,
  ObjectIds,
  OPCUAClient,
  TimestampsToReturn,
  Variant,
} from "node-opcua-client";

async function main(): Promise<void> {

  const client = OPCUAClient.create({});

  const endpointUrl = "opc.tcp://opcua.demo-this.com:62544/Quickstarts/AlarmConditionServer";

  const subscriptionParamters = {
    requestedPublishingInterval: 1000,
    maxNotificationsPerPublish: 100,
    publishingEnabled: true,
    priority: 10,
  };

  await client.withSubscriptionAsync(endpointUrl, subscriptionParamters, async (session, subscription) => {
      const fields = [
        "EventId",
        "EventType",
        "SourceNode",
        "SourceName",
        "Time",
        "ReceiveTime",
        "Message",
        "Severity",
      ];
      const eventFilter = constructEventFilter(fields);
      const event_monitoringItem = await subscription.monitor(
        {
          nodeId: ObjectIds.Server,
          attributeId: AttributeIds.EventNotifier,
        },
        {
          queueSize: 10,
          filter: eventFilter,
          discardOldest: true,
        },
        TimestampsToReturn.Both
      );

      event_monitoringItem.on("changed", (events: Variant[]) => {
        for(let i=0;i<events.length;i++) {
            console.log(fields[i],"=", events[i].toString());
        }
        console.log("----------------\n\n")
      });

      console.log("CTRL+C to stop");
      await new Promise<void>((resolve) => process.once("SIGINT", resolve));

    }
  );
}
main();
解决方案

1. 先确认自定义事件的类型和属性列表

自定义OPC UA服务器的事件通常基于BaseEventType扩展而来,直接使用字符串字段名可能因命名空间不匹配返回null。需先通过浏览获取事件类型的所有属性:

  • 连接后,浏览Server节点EventNotifier关联的事件类型,或直接查询BaseEventType的子类型(自定义事件类型)。
  • 使用session.browse获取事件类型节点的所有属性节点,记录每个属性的nodeId或带命名空间的qualifiedName。

示例代码片段:

// 在session创建后,获取BaseEventType的所有属性
const baseEventTypeNodeId = "i=2041"; // BaseEventType标准NodeId
const browseResult = await session.browse({
  nodeId: baseEventTypeNodeId,
  browseDirection: BrowseDirection.Forward,
  referenceTypeId: "i=46", // HasProperty引用类型
  includeSubtypes: true,
  nodeClassMask: NodeClass.Variable
});
// 输出所有属性的命名空间和名称
browseResult.references.forEach(ref => {
  console.log(`属性: ${ref.browseName.namespaceIndex}:${ref.browseName.name}, NodeId: ${ref.nodeId.toString()}`);
});

2. 构建包含自定义属性的EventFilter

使用constructEventFilter时,需传入带命名空间的QualifiedName对象,或直接使用属性的nodeId,而非单纯字符串:

// 假设自定义字段"CustomField"在命名空间2下
const fields = [
  "EventId",
  "EventType",
  // ... 其他标准字段
  { namespaceIndex: 2, name: "CustomField" } // 带命名空间的自定义字段
];
const eventFilter = constructEventFilter(fields);

若已获取属性nodeId,也可直接传入:

const fields = [
  // ... 标准字段
  "ns=2;s=CustomFieldNodeId" // 自定义字段的NodeId字符串
];

3. 动态生成完整EventFilter(自动获取所有字段)

如果要自动获取所有可用事件字段,可先浏览事件类型的所有属性,再动态构建fields数组:

完整修改后的代码:

import {
  AttributeIds,
  BrowseDirection,
  constructEventFilter,
  NodeClass,
  ObjectIds,
  OPCUAClient,
  TimestampsToReturn,
  Variant,
} from "node-opcua-client";

async function main(): Promise<void> {

  const client = OPCUAClient.create({});

  const endpointUrl = "opc.tcp://opcua.demo-this.com:62544/Quickstarts/AlarmConditionServer";

  const subscriptionParamters = {
    requestedPublishingInterval: 1000,
    maxNotificationsPerPublish: 100,
    publishingEnabled: true,
    priority: 10,
  };

  await client.withSubscriptionAsync(endpointUrl, subscriptionParamters, async (session, subscription) => {
      // 1. 浏览BaseEventType的所有属性(含自定义扩展)
      const baseEventTypeNodeId = "i=2041";
      const browseResult = await session.browse({
        nodeId: baseEventTypeNodeId,
        browseDirection: BrowseDirection.Forward,
        referenceTypeId: "i=46", // HasProperty引用
        includeSubtypes: true,
        nodeClassMask: NodeClass.Variable
      });

      // 2. 构建包含所有属性的fields数组
      const fields = browseResult.references.map(ref => {
        return { namespaceIndex: ref.browseName.namespaceIndex, name: ref.browseName.name };
      });
      // 补充标准必填字段(防止浏览遗漏)
      const standardFields = ["EventId", "EventType", "SourceNode", "SourceName", "Time", "ReceiveTime", "Message", "Severity"];
      standardFields.forEach(field => {
        if (!fields.some(f => f.name === field)) {
          fields.push(field);
        }
      });

      // 3. 生成EventFilter
      const eventFilter = constructEventFilter(fields);

      const event_monitoringItem = await subscription.monitor(
        {
          nodeId: ObjectIds.Server,
          attributeId: AttributeIds.EventNotifier,
        },
        {
          queueSize: 10,
          filter: eventFilter,
          discardOldest: true,
        },
        TimestampsToReturn.Both
      );

      // 4. 处理事件,匹配字段与值
      event_monitoringItem.on("changed", (events: Variant[]) => {
        console.log("收到新事件:");
        fields.forEach((field, index) => {
          const fieldName = typeof field === "string" ? field : `${field.namespaceIndex}:${field.name}`;
          console.log(`${fieldName} = ${events[index]?.toString() ?? "null"}`);
        });
        console.log("----------------\n\n")
      });

      console.log("CTRL+C to stop");
      await new Promise<void>((resolve) => process.once("SIGINT", resolve));

    }
  );
}
main();

4. 解决移除filter后空事件的问题

部分自定义OPC UA服务器要求必须指定有效EventFilter才返回事件数据,移除filter后仅返回空事件属于正常情况。使用上述动态生成filter的方法,即可获取所有可用事件字段。

内容的提问来源于stack exchange,提问作者Darkmagister

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.05 14:25:19