市面上各種收費的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下載。