Skip to content

Commit b8316a1

Browse files
authored
Support runtime/dynamic DataTypes in Server (#1374)
fixes #1371
1 parent 7c48a52 commit b8316a1

File tree

23 files changed

+1578
-1284
lines changed

23 files changed

+1578
-1284
lines changed

milo-examples/server-examples/src/main/java/org/eclipse/milo/examples/server/ExampleNamespace.java

Lines changed: 267 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2024 the Eclipse Milo Authors
2+
* Copyright (c) 2025 the Eclipse Milo Authors
33
*
44
* This program and the accompanying materials are made
55
* available under the terms of the Eclipse Public License 2.0
@@ -16,6 +16,7 @@
1616
import static org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.Unsigned.ushort;
1717

1818
import java.lang.reflect.Array;
19+
import java.util.LinkedHashMap;
1920
import java.util.List;
2021
import java.util.Random;
2122
import java.util.UUID;
@@ -29,6 +30,12 @@
2930
import org.eclipse.milo.opcua.sdk.core.ValueRank;
3031
import org.eclipse.milo.opcua.sdk.core.ValueRanks;
3132
import org.eclipse.milo.opcua.sdk.core.dtd.BinaryDataTypeCodec;
33+
import org.eclipse.milo.opcua.sdk.core.types.DynamicEnumType;
34+
import org.eclipse.milo.opcua.sdk.core.types.DynamicStructType;
35+
import org.eclipse.milo.opcua.sdk.core.types.codec.DynamicCodecFactory;
36+
import org.eclipse.milo.opcua.sdk.core.types.codec.DynamicStructCodec;
37+
import org.eclipse.milo.opcua.sdk.core.typetree.DataType;
38+
import org.eclipse.milo.opcua.sdk.core.typetree.DataTypeTree;
3239
import org.eclipse.milo.opcua.sdk.server.Lifecycle;
3340
import org.eclipse.milo.opcua.sdk.server.ManagedNamespaceWithLifecycle;
3441
import org.eclipse.milo.opcua.sdk.server.OpcUaServer;
@@ -37,6 +44,7 @@
3744
import org.eclipse.milo.opcua.sdk.server.items.DataItem;
3845
import org.eclipse.milo.opcua.sdk.server.items.MonitoredItem;
3946
import org.eclipse.milo.opcua.sdk.server.model.objects.BaseEventTypeNode;
47+
import org.eclipse.milo.opcua.sdk.server.model.objects.DataTypeEncodingTypeNode;
4048
import org.eclipse.milo.opcua.sdk.server.model.objects.ServerTypeNode;
4149
import org.eclipse.milo.opcua.sdk.server.model.variables.AnalogItemTypeNode;
4250
import org.eclipse.milo.opcua.sdk.server.nodes.UaDataTypeNode;
@@ -53,15 +61,7 @@
5361
import org.eclipse.milo.opcua.stack.core.NodeIds;
5462
import org.eclipse.milo.opcua.stack.core.OpcUaDataType;
5563
import org.eclipse.milo.opcua.stack.core.UaException;
56-
import org.eclipse.milo.opcua.stack.core.types.builtin.ByteString;
57-
import org.eclipse.milo.opcua.stack.core.types.builtin.DataValue;
58-
import org.eclipse.milo.opcua.stack.core.types.builtin.DateTime;
59-
import org.eclipse.milo.opcua.stack.core.types.builtin.ExtensionObject;
60-
import org.eclipse.milo.opcua.stack.core.types.builtin.LocalizedText;
61-
import org.eclipse.milo.opcua.stack.core.types.builtin.NodeId;
62-
import org.eclipse.milo.opcua.stack.core.types.builtin.QualifiedName;
63-
import org.eclipse.milo.opcua.stack.core.types.builtin.Variant;
64-
import org.eclipse.milo.opcua.stack.core.types.builtin.XmlElement;
64+
import org.eclipse.milo.opcua.stack.core.types.builtin.*;
6565
import org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.UInteger;
6666
import org.eclipse.milo.opcua.stack.core.types.enumerated.StructureType;
6767
import org.eclipse.milo.opcua.stack.core.types.structured.EnumDefinition;
@@ -232,6 +232,22 @@ private void createAndAddNodes() {
232232
logger.warn("Failed to register custom struct type", e);
233233
}
234234

235+
try {
236+
DataType dataType = registerDynamicStructType();
237+
238+
addDynamicStructTypeVariable(folderNode, dataType);
239+
} catch (Exception e) {
240+
logger.warn("Failed to register dynamic struct type", e);
241+
}
242+
243+
try {
244+
DataType enumDataType = registerDynamicEnumType();
245+
246+
addDynamicEnumTypeVariable(folderNode, enumDataType);
247+
} catch (Exception e) {
248+
logger.warn("Failed to register dynamic enum type", e);
249+
}
250+
235251
addCustomObjectTypeAndInstance(folderNode);
236252
}
237253

@@ -932,7 +948,7 @@ private void registerCustomStructType() throws Exception {
932948
// Register Codecs for each supported encoding with DataTypeManager
933949
getNodeContext()
934950
.getServer()
935-
.getDataTypeManager()
951+
.getStaticDataTypeManager()
936952
.registerType(dataTypeId, new CustomStructType.Codec(), binaryEncodingId, null, null);
937953

938954
// Prior to OPC UA 1.04, clients that needed to interpret custom types read the
@@ -1004,7 +1020,7 @@ private void registerCustomUnionType() throws Exception {
10041020
// Register Codecs for each supported encoding with DataTypeManager
10051021
getNodeContext()
10061022
.getServer()
1007-
.getDataTypeManager()
1023+
.getStaticDataTypeManager()
10081024
.registerType(dataTypeId, new CustomUnionType.Codec(), binaryEncodingId, null, null);
10091025

10101026
StructureDescription description =
@@ -1019,6 +1035,184 @@ private void registerCustomUnionType() throws Exception {
10191035
description);
10201036
}
10211037

1038+
private DataType registerDynamicEnumType() throws Exception {
1039+
// Define NodeId for DataType
1040+
NodeId dataTypeId =
1041+
ExpandedNodeId.parse("nsu=%s;s=DataType.DynamicEnumType".formatted(NAMESPACE_URI))
1042+
.toNodeIdOrThrow(getServer().getNamespaceTable());
1043+
1044+
// Add a custom DataTypeNode with a SubtypeOf reference to Enumeration
1045+
UaDataTypeNode dataTypeNode =
1046+
new UaDataTypeNode(
1047+
getNodeContext(),
1048+
dataTypeId,
1049+
newQualifiedName("DynamicEnumType"),
1050+
LocalizedText.english("DynamicEnumType"),
1051+
LocalizedText.NULL_VALUE,
1052+
uint(0),
1053+
uint(0),
1054+
false);
1055+
1056+
dataTypeNode.addReference(
1057+
new Reference(
1058+
dataTypeId,
1059+
NodeIds.HasSubtype,
1060+
NodeIds.Enumeration.expanded(),
1061+
Reference.Direction.INVERSE));
1062+
1063+
getNodeManager().addNode(dataTypeNode);
1064+
1065+
// Define the enum fields
1066+
EnumField[] fields =
1067+
new EnumField[] {
1068+
new EnumField(
1069+
0L, LocalizedText.english("DynField0"), LocalizedText.NULL_VALUE, "DynField0"),
1070+
new EnumField(
1071+
1L, LocalizedText.english("DynField1"), LocalizedText.NULL_VALUE, "DynField1"),
1072+
new EnumField(
1073+
2L, LocalizedText.english("DynField2"), LocalizedText.NULL_VALUE, "DynField2")
1074+
};
1075+
1076+
EnumDefinition definition = new EnumDefinition(fields);
1077+
1078+
// Set the EnumStrings property
1079+
dataTypeNode.setEnumStrings(
1080+
new LocalizedText[] {
1081+
LocalizedText.english("DynField0"),
1082+
LocalizedText.english("DynField1"),
1083+
LocalizedText.english("DynField2")
1084+
});
1085+
1086+
// Set the DataTypeDefinition attribute
1087+
dataTypeNode.setDataTypeDefinition(definition);
1088+
1089+
DataTypeTree dataTypeTree = getNodeContext().getServer().updateDataTypeTree();
1090+
DataType dataType = dataTypeTree.getDataType(dataTypeId);
1091+
assert dataType != null;
1092+
1093+
// Register with the dynamic DataTypeManager
1094+
getNodeContext()
1095+
.getServer()
1096+
.getDynamicDataTypeManager()
1097+
.registerType(
1098+
dataTypeId, DynamicCodecFactory.create(dataType, dataTypeTree), null, null, null);
1099+
1100+
return dataType;
1101+
}
1102+
1103+
private DataType registerDynamicStructType() throws Exception {
1104+
// Define NodeIds for DataType and encoding Nodes
1105+
NodeId dataTypeId =
1106+
ExpandedNodeId.parse("nsu=%s;s=DataType.DynamicStructType".formatted(NAMESPACE_URI))
1107+
.toNodeIdOrThrow(getServer().getNamespaceTable());
1108+
1109+
NodeId binaryEncodingId =
1110+
ExpandedNodeId.parse(
1111+
"nsu=%s;s=DataType.DynamicStructType.BinaryEncoding".formatted(NAMESPACE_URI))
1112+
.toNodeIdOrThrow(getServer().getNamespaceTable());
1113+
1114+
// Add a custom DataTypeNode with a SubtypeOf reference to Structure
1115+
UaDataTypeNode dataTypeNode =
1116+
new UaDataTypeNode(
1117+
getNodeContext(),
1118+
dataTypeId,
1119+
newQualifiedName("DynamicStructType"),
1120+
LocalizedText.english("DynamicStructType"),
1121+
LocalizedText.NULL_VALUE,
1122+
uint(0),
1123+
uint(0),
1124+
false);
1125+
1126+
dataTypeNode.addReference(
1127+
new Reference(
1128+
dataTypeId,
1129+
NodeIds.HasSubtype,
1130+
NodeIds.Structure.expanded(),
1131+
Reference.Direction.INVERSE));
1132+
1133+
getNodeManager().addNode(dataTypeNode);
1134+
1135+
// Add a DataTypeEncodingNode for binary encoding of the new DataType
1136+
DataTypeEncodingTypeNode dataTypeEncodingNode =
1137+
new DataTypeEncodingTypeNode(
1138+
getNodeContext(),
1139+
binaryEncodingId,
1140+
new QualifiedName(0, "Default Binary"),
1141+
LocalizedText.english("Default Binary"),
1142+
LocalizedText.NULL_VALUE,
1143+
uint(0),
1144+
uint(0),
1145+
null,
1146+
null,
1147+
null);
1148+
1149+
dataTypeEncodingNode.addReference(
1150+
new Reference(
1151+
dataTypeEncodingNode.getNodeId(),
1152+
NodeIds.HasTypeDefinition,
1153+
NodeIds.DataTypeEncodingType.expanded(),
1154+
Reference.Direction.FORWARD));
1155+
1156+
dataTypeEncodingNode.addReference(
1157+
new Reference(
1158+
dataTypeEncodingNode.getNodeId(),
1159+
NodeIds.HasEncoding,
1160+
dataTypeId.expanded(),
1161+
Reference.Direction.INVERSE));
1162+
1163+
getNodeManager().addNode(dataTypeEncodingNode);
1164+
1165+
// Define the structure
1166+
StructureField[] fields =
1167+
new StructureField[] {
1168+
new StructureField(
1169+
"foo",
1170+
LocalizedText.NULL_VALUE,
1171+
NodeIds.String,
1172+
ValueRanks.Scalar,
1173+
null,
1174+
getServer().getConfig().getLimits().getMaxStringLength(),
1175+
false),
1176+
new StructureField(
1177+
"bar",
1178+
LocalizedText.NULL_VALUE,
1179+
NodeIds.UInt32,
1180+
ValueRanks.Scalar,
1181+
null,
1182+
uint(0),
1183+
false),
1184+
new StructureField(
1185+
"baz",
1186+
LocalizedText.NULL_VALUE,
1187+
NodeIds.Boolean,
1188+
ValueRanks.Scalar,
1189+
null,
1190+
uint(0),
1191+
false)
1192+
};
1193+
1194+
StructureDefinition definition =
1195+
new StructureDefinition(
1196+
binaryEncodingId, NodeIds.Structure, StructureType.Structure, fields);
1197+
1198+
// Set the DataTypeDefinition attribute
1199+
dataTypeNode.setDataTypeDefinition(definition);
1200+
1201+
DataTypeTree dataTypeTree = getNodeContext().getServer().updateDataTypeTree();
1202+
DataType dataType = dataTypeTree.getDataType(dataTypeId);
1203+
assert dataType != null;
1204+
1205+
// Register Codecs for each supported encoding with DataTypeManager
1206+
var codec = new DynamicStructCodec(dataType, dataTypeTree);
1207+
1208+
getNodeContext()
1209+
.getServer()
1210+
.getDynamicDataTypeManager()
1211+
.registerType(dataTypeId, codec, binaryEncodingId, null, null);
1212+
1213+
return dataType;
1214+
}
1215+
10221216
private void addCustomEnumTypeVariable(UaFolderNode rootFolder) throws Exception {
10231217
NodeId dataTypeId = CustomEnumType.TYPE_ID.toNodeIdOrThrow(getServer().getNamespaceTable());
10241218

@@ -1070,7 +1264,7 @@ private void addCustomStructTypeVariable(UaFolderNode rootFolder) throws Excepti
10701264

10711265
ExtensionObject xo =
10721266
ExtensionObject.encodeDefaultBinary(
1073-
getServer().getEncodingContext(), value, binaryEncodingId);
1267+
getServer().getStaticEncodingContext(), value, binaryEncodingId);
10741268

10751269
customStructTypeVariable.setValue(new DataValue(new Variant(xo)));
10761270

@@ -1107,7 +1301,7 @@ private void addCustomUnionTypeVariable(UaFolderNode rootFolder) throws Exceptio
11071301

11081302
ExtensionObject xo =
11091303
ExtensionObject.encodeDefaultBinary(
1110-
getServer().getEncodingContext(), value, binaryEncodingId);
1304+
getServer().getStaticEncodingContext(), value, binaryEncodingId);
11111305

11121306
customUnionTypeVariable.setValue(new DataValue(new Variant(xo)));
11131307

@@ -1121,6 +1315,65 @@ private void addCustomUnionTypeVariable(UaFolderNode rootFolder) throws Exceptio
11211315
false));
11221316
}
11231317

1318+
private void addDynamicEnumTypeVariable(UaFolderNode rootFolder, DataType dataType) {
1319+
NodeId nodeId = newNodeId("HelloWorld/DynamicEnumTypeVariable");
1320+
1321+
UaVariableNode dynamicEnumTypeVariable =
1322+
new UaVariableNode.UaVariableNodeBuilder(getNodeContext())
1323+
.setNodeId(nodeId)
1324+
.setAccessLevel(AccessLevel.READ_WRITE)
1325+
.setUserAccessLevel(AccessLevel.READ_WRITE)
1326+
.setBrowseName(newQualifiedName("DynamicEnumTypeVariable"))
1327+
.setDisplayName(LocalizedText.english("DynamicEnumTypeVariable"))
1328+
.setDataType(dataType.getNodeId())
1329+
.setTypeDefinition(NodeIds.BaseDataVariableType)
1330+
.build();
1331+
1332+
// Create an instance of DynamicEnumType with value 0 (DynField0)
1333+
DynamicEnumType value = DynamicEnumType.newInstance(dataType, 0);
1334+
dynamicEnumTypeVariable.setValue(new DataValue(new Variant(value)));
1335+
1336+
getNodeManager().addNode(dynamicEnumTypeVariable);
1337+
rootFolder.addOrganizes(dynamicEnumTypeVariable);
1338+
}
1339+
1340+
private void addDynamicStructTypeVariable(UaFolderNode rootFolder, DataType dataType) {
1341+
UaVariableNode dynamicStructTypeVariable =
1342+
UaVariableNode.build(
1343+
getNodeContext(),
1344+
b ->
1345+
b.setNodeId(newNodeId("HelloWorld/DynamicStructTypeVariable"))
1346+
.setAccessLevel(AccessLevel.READ_WRITE)
1347+
.setUserAccessLevel(AccessLevel.READ_WRITE)
1348+
.setBrowseName(newQualifiedName("DynamicStructTypeVariable"))
1349+
.setDisplayName(LocalizedText.english("DynamicStructTypeVariable"))
1350+
.setDataType(dataType.getNodeId())
1351+
.setTypeDefinition(NodeIds.BaseDataVariableType)
1352+
.build());
1353+
1354+
LinkedHashMap<String, Object> members = new LinkedHashMap<>();
1355+
members.put("foo", "foo");
1356+
members.put("bar", uint(42));
1357+
members.put("baz", true);
1358+
DynamicStructType value = new DynamicStructType(dataType, members);
1359+
1360+
// Note that we're using getDynamicEncodingContext() here
1361+
ExtensionObject xo =
1362+
ExtensionObject.encodeDefaultBinary(
1363+
getServer().getDynamicEncodingContext(), value, dataType.getBinaryEncodingId());
1364+
1365+
dynamicStructTypeVariable.setValue(new DataValue(Variant.ofExtensionObject(xo)));
1366+
1367+
getNodeManager().addNode(dynamicStructTypeVariable);
1368+
1369+
dynamicStructTypeVariable.addReference(
1370+
new Reference(
1371+
dynamicStructTypeVariable.getNodeId(),
1372+
NodeIds.Organizes,
1373+
rootFolder.getNodeId().expanded(),
1374+
false));
1375+
}
1376+
11241377
@Override
11251378
public void onDataItemsCreated(List<DataItem> dataItems) {
11261379
subscriptionModel.onDataItemsCreated(dataItems);

opc-ua-sdk/dtd-manager/src/main/java/org/eclipse/milo/opcua/sdk/server/dtd/BinaryDataTypeDictionaryManager.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2024 the Eclipse Milo Authors
2+
* Copyright (c) 2025 the Eclipse Milo Authors
33
*
44
* This program and the accompanying materials are made
55
* available under the terms of the Eclipse Public License 2.0
@@ -124,7 +124,7 @@ private UaNodeManager getNodeManager() {
124124

125125
@Override
126126
public void startup() {
127-
context.getServer().getDataTypeManager().registerTypeDictionary(dataTypeDictionary);
127+
context.getServer().getStaticDataTypeManager().registerTypeDictionary(dataTypeDictionary);
128128

129129
// Add a DataTypeDictionary Node...
130130
dictionaryNode =
@@ -321,7 +321,7 @@ public void registerStructure(
321321

322322
context
323323
.getServer()
324-
.getDataTypeManager()
324+
.getStaticDataTypeManager()
325325
.registerType(dataTypeId, codec, binaryEncodingId, null, null);
326326
}
327327

0 commit comments

Comments
 (0)