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下載。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章