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

OPC UA客户端多订阅多NodeId异常:仅接收每个订阅最后一个NodeId值

OPC UA客户端订阅问题:每个订阅仅接收最后一个NodeId的值

问题描述

我编写了一个OPC UA客户端代码,创建10个订阅,每个订阅处理3个NodeId。尽管已为每个NodeId创建了监控项,但每个订阅仅能接收到该订阅中最后一个NodeId的值。

原代码

import org.eclipse.milo.opcua.sdk.client.OpcUaClient;
import org.eclipse.milo.opcua.sdk.client.subscriptions.UaMonitoredItem;
import org.eclipse.milo.opcua.sdk.client.subscriptions.UaSubscription;
import org.eclipse.milo.opcua.stack.core.AttributeId;
import org.eclipse.milo.opcua.stack.core.types.builtin.DataValue;
import org.eclipse.milo.opcua.stack.core.types.builtin.NodeId;
import org.eclipse.milo.opcua.stack.core.types.builtin.QualifiedName;
import org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.UInteger;
import org.eclipse.milo.opcua.stack.core.types.enumerated.MonitoringMode;
import org.eclipse.milo.opcua.stack.core.types.enumerated.TimestampsToReturn;
import org.eclipse.milo.opcua.stack.core.types.structured.MonitoredItemCreateRequest;
import org.eclipse.milo.opcua.stack.core.types.structured.MonitoringParameters;
import org.eclipse.milo.opcua.stack.core.types.structured.ReadValueId;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.List;
import static org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.Unsigned.uint;
import static com.google.common.collect.Lists.newArrayList;

public class MultipleSubscriptionExample {
    private static final Logger logger = LoggerFactory.getLogger(MultipleSubscriptionExample.class);

    public static void main(String[] args) throws Exception {
        OpcUaClient client = OpcUaClient.create("opc.tcp://KCT-L-610:53530/OPCUA/SimulationServer");
        client.connect().get();

        // 创建10个订阅,每个订阅处理3个标签
        for (int i = 0; i < 10; i++) {
            createSubscription(client, i);
        }

        // 休眠以让订阅运行并收集数据
        Thread.sleep(10000);
        client.disconnect().get();
    }

    private static void createSubscription(OpcUaClient client, int subscriptionIndex) throws Exception {
        // 每次创建新订阅
        UaSubscription subscription = client.getSubscriptionManager().createSubscription(1000.0).get();

        // 定义当前订阅要监控的3个节点
        List<ReadValueId> readValueIds = newArrayList(
            new ReadValueId(new NodeId(3, 1007 + subscriptionIndex * 3), AttributeId.Value.uid(), null, QualifiedName.NULL_VALUE),
            new ReadValueId(new NodeId(3, 1008 + subscriptionIndex * 3), AttributeId.Value.uid(), null, QualifiedName.NULL_VALUE),
            new ReadValueId(new NodeId(3, 1009 + subscriptionIndex * 3), AttributeId.Value.uid(), null, QualifiedName.NULL_VALUE)
        );

        // 创建订阅的监控参数
        MonitoringParameters parameters = new MonitoringParameters(
            subscription.nextClientHandle(),
            1000.0, // 采样间隔:1秒
            null,   // 无过滤器,使用默认
            uint(10),  // 队列大小
            true    // 队列满时丢弃最旧值
        );
        
        // 创建监控项请求
        List<MonitoredItemCreateRequest> requests = newArrayList();
        for (ReadValueId readValueId : readValueIds) {
            requests.add(new MonitoredItemCreateRequest(readValueId, MonitoringMode.Reporting, parameters));
        }

        // 创建监控项并设置值更新回调
        UaSubscription.ItemCreationCallback onItemCreated = (item, id) -> {
            item.setValueConsumer((item1, value) -> onSubscriptionValue(item1, value, subscriptionIndex));
        };

        // 创建监控项并处理结果
        List<UaMonitoredItem> items = subscription.createMonitoredItems(
            TimestampsToReturn.Both,
            requests,
            onItemCreated
        ).get();

        for (UaMonitoredItem item : items) {
            if (item.getStatusCode().isGood()) {
                logger.info("Subscription {}: 为nodeId={}创建监控项成功", subscriptionIndex, item.getReadValueId().getNodeId());
            } else {
                logger.warn("Subscription {}: 为nodeId={}创建监控项失败 (状态码={})", subscriptionIndex, item.getReadValueId().getNodeId(), item.getStatusCode());
            }
        }
    }

    private static void onSubscriptionValue(UaMonitoredItem item, DataValue value, int subscriptionIndex) {
        // 监控项值更新时触发此方法
        logger.info("Subscription {}: 收到nodeId={}的值: {}", subscriptionIndex, item.getReadValueId().getNodeId(), value.getValue());
    }
}

运行输出

14:56:45.402 [milo-shared-thread-pool-3] INFO  c.m.o.MultipleSubscriptionExample - Subscription 0: Received value for nodeId=NodeId{ns=3, id=1009}: Variant{value=0.0}
14:56:45.403 [milo-shared-thread-pool-3] INFO  c.m.o.MultipleSubscriptionExample - Subscription 0: Received value for nodeId=NodeId{ns=3, id=1009}: Variant{value=-1.618126}
14:56:45.403 [milo-shared-thread-pool-3] INFO  c.m.o.MultipleSubscriptionExample - Subscription 0: Received value for nodeId=NodeId{ns=3, id=1009}: Variant{value=-2.0}
14:56:45.413 [milo-shared-thread-pool-3] INFO  c.m.o.MultipleSubscriptionExample - Subscription 1: Received value for nodeId=NodeId{ns=3, id=1012}: Variant{value=0.1333334}
14:56:45.417 [milo-shared-thread-pool-3] INFO  c.m.o.MultipleSubscriptionExample - Subscription 1: Received value for nodeId=NodeId{ns=3, id=1012}: Variant{value=27.0}
14:56:45.417 [milo-shared-thread-pool-3] INFO  c.m.o.MultipleSubscriptionExample - Subscription 1: Received value for nodeId=NodeId{ns=3, id=1012}: Variant{value=0.0}
14:56:45.417 [milo-shared-thread-pool-3] INFO  c.m.o.MultipleSubscriptionExample - Subscription 1: Received value for nodeId=NodeId{ns=3, id=1012}: Variant{value=28.0}
14:56:45.427 [milo-shared-thread-pool-3] INFO  c.m.o.MultipleSubscriptionExample - Subscription 2: Received value for nodeId=NodeId{ns=3, id=1015}: Variant{value=-5.235019}
14:56:45.427 [milo-shared-thread-pool-3] INFO  c.m.o.MultipleSubscriptionExample - Subscription 2: Received value for nodeId=NodeId{ns=3, id=1015}: Variant{value=0.209057}
14:56:45.427 [milo-shared-thread-pool-3] INFO  c.m.o.MultipleSubscriptionExample - Subscription 2: Received value for nodeId=NodeId{ns=3, id=1015}: Variant{value=-4.472503}
14:56:45.427 [milo-shared-thread-pool-3] INFO  c.m.o.MultipleSubscriptionExample - Subscription 2: Received value for nodeId=NodeId{ns=3, id=1015}: Variant{value=0.0}

问题原因及解决方案

问题根源

所有监控项共用了同一个MonitoringParameters实例,且仅调用了一次subscription.nextClientHandle()生成客户端句柄。OPC UA服务器通过唯一的客户端句柄区分不同监控项,重复句柄会导致服务器覆盖之前的监控项配置,最终只有最后一个监控项的更新能被正确推送。

修改方案

在创建MonitoredItemCreateRequest的循环中,为每个监控项生成独立的MonitoringParameters,确保每个监控项的客户端句柄唯一:

关键修改代码段

// 修改创建监控项请求的逻辑
List<MonitoredItemCreateRequest> requests = newArrayList();
for (ReadValueId readValueId : readValueIds) {
    // 每个监控项使用独立的MonitoringParameters,获取新的客户端句柄
    MonitoringParameters itemParams = new MonitoringParameters(
        subscription.nextClientHandle(),
        1000.0,
        null,
        uint(10),
        true
    );
    requests.add(new MonitoredItemCreateRequest(readValueId, MonitoringMode.Reporting, itemParams));
}

完整修改后的createSubscription方法

private static void createSubscription(OpcUaClient client, int subscriptionIndex) throws Exception {
    UaSubscription subscription = client.getSubscriptionManager().createSubscription(1000.0).get();

    List<ReadValueId> readValueIds = newArrayList(
        new ReadValueId(new NodeId(3, 1007 + subscriptionIndex * 3), AttributeId.Value.uid(), null, QualifiedName.NULL_VALUE),
        new ReadValueId(new NodeId(3, 1008 + subscriptionIndex * 3), AttributeId.Value.uid(), null, QualifiedName.NULL_VALUE),
        new ReadValueId(new NodeId(3, 1009 + subscriptionIndex * 3), AttributeId.Value.uid(), null, QualifiedName.NULL_VALUE)
    );
    
    List<MonitoredItemCreateRequest> requests = newArrayList();
    for (ReadValueId readValueId : readValueIds) {
        // 每个监控项分配独立的监控参数,保证句柄唯一
        MonitoringParameters itemParams = new MonitoringParameters(
            subscription.nextClientHandle(),
            1000.0,
            null,
            uint(10),
            true
        );
        requests.add(new MonitoredItemCreateRequest(readValueId, MonitoringMode.Reporting, itemParams));
    }

    UaSubscription.ItemCreationCallback onItemCreated = (item, id) -> {
        item.setValueConsumer((item1, value) -> onSubscriptionValue(item1, value, subscriptionIndex));
    };

    List<UaMonitoredItem> items = subscription.createMonitoredItems(
        TimestampsToReturn.Both,
        requests,
        onItemCreated
    ).get();

    for (UaMonitoredItem item : items) {
        if (item.getStatusCode().isGood()) {
            logger.info("Subscription {}: 为nodeId={}创建监控项成功", subscriptionIndex, item.getReadValueId().getNodeId());
        } else {
            logger.warn("Subscription {}: 为nodeId={}创建监控项失败 (状态码={})", subscriptionIndex, item.getReadValueId().getNodeId(), item.getStatusCode());
        }
    }
}

修改后,每个订阅的3个NodeId都能正常接收值更新。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.15 08:39:54