如何通过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
相关产品推荐
相关产品推荐

