OPC UA JAVA开发笔记(五):milo nodeparser解析XML文件获取结点集

市面上各种收费的UA软件都有一个诱人的功能,那就是直接解析XML获取结点集,而开源的OPC库中,据我所知只有open62541有,这可以极大的简化我们的流程,并且OPC UA Foundation已经建立了相应的NodeSet文件。

这里我们采用milo的nodeparser来解析XML文件。
版本要求是milo的-0.4.0-SNAPSHOT或以上

<dependency>
            <groupId>org.eclipse.milo</groupId>
            <artifactId>sdk-server</artifactId>
            <version>0.4.0-SNAPSHOT</version>
        </dependency>

本文发布时暂没有相应的版本发布,大家可以等一下0.4版本发布,或者和我一样直接在Github上已有的源码基础上更改。

我们将这个地址的代码clone到本地nodeparser-milo
克隆好后,我们需要先做一个步骤。将克隆后的一个文件 UANodeSet.xsd 复制到任意位置,比如我是在桌面。然后我们通过xsd来生成对应的Java文件,详情可参考XSD生成java文件

如果不管用就再找找其他教程,核心就是通过xsd生成java类

接下来将nodeparser中的以下文件,复制到milo的module server-example 中。
在这里插入图片描述
这里的generated文件下的类就是我们通过xsd生成的所有类。

复制完后,肯定会飘红,主要是要改一下import的目录,把以前的

  • import org.opcfoundation.ua.generated
  • 统统改成 import nodeset.generated.;

其实就是把generated文件夹下的import修正一下
还有就是这里的NodeUtils是后面创建的,现在先不管

接着我们新建一个CncNamespace的类
在这里插入图片描述
内容如下:

public class CNCNamespace extends ManagedNamespace {

    public static final String NAMESPACE_URI = "urn:eclipse:milo:CNC";

    private final Logger logger = LoggerFactory.getLogger(getClass());

    private final Random random = new Random();

    private final DataTypeDictionaryManager dictionaryManager;

    private final SubscriptionModel subscriptionModel;

    CNCNamespace(OpcUaServer server) {
        super(server, NAMESPACE_URI);

        subscriptionModel = new SubscriptionModel(server, this);

        dictionaryManager = new DataTypeDictionaryManager(getNodeContext(), NAMESPACE_URI);
    }

    @Override
    protected void onStartup() {
        super.onStartup();

        dictionaryManager.startup();
        subscriptionModel.startup();

        // Create a "HelloWorld" folder and add it to the node manager
        NodeId folderNodeId = newNodeId("CNC");

        UaFolderNode folderNode = new UaFolderNode(
                getNodeContext(),
                folderNodeId,
                newQualifiedName("CNC"),
                LocalizedText.english("CNC")
        );

        getNodeManager().addNode(folderNode);

        // Make sure our new folder shows up under the server's Objects folder.
        folderNode.addReference(new Reference(
                folderNode.getNodeId(),
                Identifiers.Organizes,
                Identifiers.ObjectsFolder.expanded(),
                false
        ));

        addCNCNodes();

        // Set the EventNotifier bit on Server Node for Events.
        UaNode serverNode = getServer()
                .getAddressSpaceManager()
                .getManagedNode(Identifiers.Server)
                .orElse(null);

        if (serverNode instanceof ServerTypeNode) {
            ((ServerTypeNode) serverNode).setEventNotifier(ubyte(1));

            // Post a bogus Event every couple seconds
            getServer().getScheduledExecutorService().scheduleAtFixedRate(() -> {
                try {
                    BaseEventTypeNode eventNode = getServer().getEventFactory().createEvent(
                            newNodeId(UUID.randomUUID()),
                            Identifiers.BaseEventType
                    );

                    eventNode.setBrowseName(new QualifiedName(1, "foo"));
                    eventNode.setDisplayName(LocalizedText.english("foo"));
                    eventNode.setEventId(ByteString.of(new byte[]{0, 1, 2, 3}));
                    eventNode.setEventType(Identifiers.BaseEventType);
                    eventNode.setSourceNode(serverNode.getNodeId());
                    eventNode.setSourceName(serverNode.getDisplayName().getText());
                    eventNode.setTime(DateTime.now());
                    eventNode.setReceiveTime(DateTime.NULL_VALUE);
                    eventNode.setMessage(LocalizedText.english("event message!"));
                    eventNode.setSeverity(ushort(2));

                    getServer().getEventBus().post(eventNode);

                    eventNode.delete();
                } catch (Throwable e) {
                    logger.error("Error creating EventNode: {}", e.getMessage(), e);
                }
            }, 0, 2, TimeUnit.SECONDS);
        }
    }

    @Override
    protected void onShutdown() {
        dictionaryManager.shutdown();
        subscriptionModel.shutdown();

        super.onShutdown();
    }

    private void addCNCNodes() {
        InputStream nodeSetXml = getClass().getClassLoader().getResourceAsStream("Opc.Ua.CNC.NodeSet.xml");

        UaNodeSet nodeSet;
        try {
            nodeSet = UaNodeSet.parse(nodeSetXml);
            parseNodeSet(nodeSet,getNodeContext(),getNodeManager());
        } catch (JAXBException e) {
            logger.error(e.getMessage());
        }
    }

    @Override
    public void onDataItemsCreated(List<DataItem> dataItems) {
        subscriptionModel.onDataItemsCreated(dataItems);
    }

    @Override
    public void onDataItemsModified(List<DataItem> dataItems) {
        subscriptionModel.onDataItemsModified(dataItems);
    }

    @Override
    public void onDataItemsDeleted(List<DataItem> dataItems) {
        subscriptionModel.onDataItemsDeleted(dataItems);
    }

    @Override
    public void onMonitoringModeChanged(List<MonitoredItem> monitoredItems) {
        subscriptionModel.onMonitoringModeChanged(monitoredItems);
    }
}

核心是以下方法:

private void addCNCNodes() {
        InputStream nodeSetXml = getClass().getClassLoader().getResourceAsStream("Opc.Ua.CNC.NodeSet.xml");

        UaNodeSet nodeSet;
        try {
            nodeSet = UaNodeSet.parse(nodeSetXml);
            parseNodeSet(nodeSet,getNodeContext(),getNodeManager());
        } catch (JAXBException e) {
            logger.error(e.getMessage());
        }
    }

这里的parseNodeSet方法需要我们在刚刚提到的NodeSetUtils类中创建,位置通刚才一样,代码如下

@Slf4j
public class NodeUtils {

    public static void parseNodeSet(UaNodeSet nodeSet, UaNodeContext context, UaNodeManager nodeManager){
        Map<NodeId, NodeAttributes> nodes = nodeSet.getNodes();
        ListMultimap<NodeId, Reference> allReferences = nodeSet.getAllReferences();

        Map<NodeClass, List<NodeAttributes>> collect = nodes.values().stream()
                .collect(groupingBy(NodeAttributes::getNodeClass));
                
		//依据不同的类别在nodeManager中添加结点
        for (NodeClass nodeClass : collect.keySet()) {
            if (nodeClass.equals(NodeClass.ObjectType)) {
                collect.get(NodeClass.ObjectType).stream()
                        .map(node -> ((ObjectTypeNodeAttributes) node))
                        .map(node -> node.getUaObjectTypeNode(context))
                        .forEach(nodeManager::addNode);
                log.info("ObjectType success");
            } else if (nodeClass.equals(NodeClass.VariableType)) {
                collect.get(NodeClass.VariableType).stream()
                        .map(node -> ((VariableTypeNodeAttributes) node))
                        .map(node -> node.getUaVariableTypeNode(context))
                        .forEach(nodeManager::addNode);
                log.info("VariableType success");
            } else if (nodeClass.equals(NodeClass.Object)) {
                collect.get(NodeClass.Object).stream()
                        .map(node -> ((ObjectNodeAttributes) node))
                        .map(node -> node.getObjectNode(context))
                        .forEach(nodeManager::addNode);
                log.info("Object success");
            } else if (nodeClass.equals(NodeClass.Variable)) {
                collect.get(NodeClass.Variable).stream()
                        .map(node -> ((VariableNodeAttributes) node))
                        .map(node -> node.getUaVariableNode(context))
                        .forEach(nodeManager::addNode);
                log.info("Variable success");
            } else if (nodeClass.equals(NodeClass.Method)) {
                collect.get(NodeClass.Method).stream()
                        .map(node -> ((MethodNodeAttributes) node))
                        .map(node -> node.getUaMethodNode(context))
                        .forEach(nodeManager::addNode);
                log.info("Method success");
            } else if (nodeClass.equals(NodeClass.DataType)) {
                collect.get(NodeClass.DataType).stream()
                        .map(node -> ((DataTypeNodeAttributes) node))
                        .map(node -> node.getDataTypeNode(context))
                        .forEach(nodeManager::addNode);
                log.info("DataType success");
            } else if (nodeClass.equals(NodeClass.View)) {
                collect.get(NodeClass.View).stream()
                        .map(node -> ((ViewNodeAttributes) node))
                        .map(node -> node.getUaViewNode(context))
                        .forEach(nodeManager::addNode);
                log.info("View success");
            } else if (nodeClass.equals(NodeClass.ReferenceType)) {
                collect.get(NodeClass.ReferenceType).stream()
                        .map(node -> ((ReferenceTypeNodeAttributes) node))
                        .map(node -> node.getReferenceTypeNode(context))
                        .forEach(nodeManager::addNode);
                log.info("ReferenceType success");
            }
        }
		//在nodeManager中添加所有的引用
        allReferences.values()
                .forEach(nodeManager::addReference);
    }
}

我们以其中一个添加的方法为例做分析,其他的大家应该就都懂了

if (nodeClass.equals(NodeClass.Object)) {
                collect.get(NodeClass.Object).stream()
                        .map(node -> ((ObjectNodeAttributes) node))
                        .map(node -> node.getObjectNode(context))
                        .forEach(nodeManager::addNode);
                log.info("ObjectType success");

我们是根据其结点的类型,然后将其转换为对应的ObjectTypeNodeAttribute类,并调用此类的getUaObjectNode方法,得到对应UaObjectTypeNode类

而对于getUaOBjectTypeNode方法,我们需要在ObjectTypeNodeAttribute类中自己定义,代码如下

public class ObjectNodeAttributes extends NodeAttributes {    

    /*其他部分省略,只展示添加的方法*/
    
    public UaObjectNode getObjectNode(UaNodeContext context){
        return new UaObjectNode(
                context,
                getNodeId(),
                getBrowseName(),
                getDisplayName(),
                getDescription(),
                getWriteMask(),
                getUserWriteMask(),
                getEventNotifier()
        );
    }
}

可以看出,其实就是重新定义了一个ObjectNode然后通过new 创建一个新的UaObjectNode对象。其他的类大致如此,注意对比原来的UaNode的构造方法是怎样的。

最后我们在ExampleServer的构造方法中添加新的命名空间

public ExampleServer() throws Exception {
	/*其他部分省略*/
	ExampleNamespace exampleNamespace = new ExampleNamespace(server);
	//添加命名空间,exampleNamespce的ns id是2
    exampleNamespace.startup();
    CNCNamespace cncNamespace = new CNCNamespace(server);
   	//添加命名空间,cncNamespce的ns id是3
    cncNamespace.startup();
}

最后执行程序,通过uaexpert检验,成功:
在这里插入图片描述
这样就刻意解析所有的NodeSet啦,具体的nodeset文件可以在OPC UA Foundation下载。

猜你喜欢

转载自blog.csdn.net/qq_41989109/article/details/104730047