MyNamespace.java 35 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850
  1. package at.acdp.opcur.opc;
  2. /*
  3. * Copyright (c) 2016 Kevin Herron
  4. *
  5. * All rights reserved. This program and the accompanying materials
  6. * are made available under the terms of the Eclipse Public License v1.0
  7. * and Eclipse Distribution License v1.0 which accompany this distribution.
  8. *
  9. * The Eclipse Public License is available at
  10. * http://www.eclipse.org/legal/epl-v10.html
  11. * and the Eclipse Distribution License is available at
  12. * http://www.eclipse.org/org/documents/edl-v10.html.
  13. */
  14. import java.lang.reflect.Array;
  15. import java.util.List;
  16. import java.util.Optional;
  17. import java.util.Random;
  18. import java.util.UUID;
  19. import java.util.concurrent.CompletableFuture;
  20. import com.google.common.collect.Lists;
  21. import org.eclipse.milo.opcua.sdk.core.AccessLevel;
  22. import org.eclipse.milo.opcua.sdk.core.Reference;
  23. import org.eclipse.milo.opcua.sdk.core.ValueRank;
  24. import org.eclipse.milo.opcua.sdk.server.OpcUaServer;
  25. import org.eclipse.milo.opcua.sdk.server.api.AccessContext;
  26. import org.eclipse.milo.opcua.sdk.server.api.DataItem;
  27. import org.eclipse.milo.opcua.sdk.server.api.MethodInvocationHandler;
  28. import org.eclipse.milo.opcua.sdk.server.api.MonitoredItem;
  29. import org.eclipse.milo.opcua.sdk.server.api.Namespace;
  30. import org.eclipse.milo.opcua.sdk.server.api.nodes.VariableNode;
  31. import org.eclipse.milo.opcua.sdk.server.model.nodes.variables.AnalogItemNode;
  32. import org.eclipse.milo.opcua.sdk.server.nodes.AttributeContext;
  33. import org.eclipse.milo.opcua.sdk.server.nodes.NodeFactory;
  34. import org.eclipse.milo.opcua.sdk.server.nodes.ServerNode;
  35. import org.eclipse.milo.opcua.sdk.server.nodes.UaDataTypeNode;
  36. import org.eclipse.milo.opcua.sdk.server.nodes.UaFolderNode;
  37. import org.eclipse.milo.opcua.sdk.server.nodes.UaMethodNode;
  38. import org.eclipse.milo.opcua.sdk.server.nodes.UaObjectNode;
  39. import org.eclipse.milo.opcua.sdk.server.nodes.UaObjectTypeNode;
  40. import org.eclipse.milo.opcua.sdk.server.nodes.UaVariableNode;
  41. import org.eclipse.milo.opcua.sdk.server.nodes.delegates.AttributeDelegate;
  42. import org.eclipse.milo.opcua.sdk.server.nodes.delegates.AttributeDelegateChain;
  43. import org.eclipse.milo.opcua.sdk.server.util.AnnotationBasedInvocationHandler;
  44. import org.eclipse.milo.opcua.sdk.server.util.SubscriptionModel;
  45. import org.eclipse.milo.opcua.stack.core.AttributeId;
  46. import org.eclipse.milo.opcua.stack.core.Identifiers;
  47. import org.eclipse.milo.opcua.stack.core.StatusCodes;
  48. import org.eclipse.milo.opcua.stack.core.UaException;
  49. import org.eclipse.milo.opcua.stack.core.types.OpcUaBinaryDataTypeDictionary;
  50. import org.eclipse.milo.opcua.stack.core.types.OpcUaDataTypeManager;
  51. import org.eclipse.milo.opcua.stack.core.types.builtin.ByteString;
  52. import org.eclipse.milo.opcua.stack.core.types.builtin.DataValue;
  53. import org.eclipse.milo.opcua.stack.core.types.builtin.DateTime;
  54. import org.eclipse.milo.opcua.stack.core.types.builtin.ExtensionObject;
  55. import org.eclipse.milo.opcua.stack.core.types.builtin.LocalizedText;
  56. import org.eclipse.milo.opcua.stack.core.types.builtin.NodeId;
  57. import org.eclipse.milo.opcua.stack.core.types.builtin.QualifiedName;
  58. import org.eclipse.milo.opcua.stack.core.types.builtin.StatusCode;
  59. import org.eclipse.milo.opcua.stack.core.types.builtin.Variant;
  60. import org.eclipse.milo.opcua.stack.core.types.builtin.XmlElement;
  61. import org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.UInteger;
  62. import org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.UShort;
  63. import org.eclipse.milo.opcua.stack.core.types.enumerated.NodeClass;
  64. import org.eclipse.milo.opcua.stack.core.types.enumerated.TimestampsToReturn;
  65. import org.eclipse.milo.opcua.stack.core.types.structured.Range;
  66. import org.eclipse.milo.opcua.stack.core.types.structured.ReadValueId;
  67. import org.eclipse.milo.opcua.stack.core.types.structured.WriteValue;
  68. import org.eclipse.milo.opcua.stack.core.util.FutureUtils;
  69. import org.slf4j.Logger;
  70. import org.slf4j.LoggerFactory;
  71. import static org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.Unsigned.ubyte;
  72. import static org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.Unsigned.uint;
  73. import static org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.Unsigned.ulong;
  74. import static org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.Unsigned.ushort;
  75. public class MyNamespace implements Namespace {
  76. public static final String NAMESPACE_URI = "urn:eclipse:milo:hello-world";
  77. private static final Object[][] STATIC_SCALAR_NODES = new Object[][]{
  78. {"Boolean", Identifiers.Boolean, new Variant(false)},
  79. {"Byte", Identifiers.Byte, new Variant(ubyte(0x00))},
  80. {"SByte", Identifiers.SByte, new Variant((byte) 0x00)},
  81. {"Integer", Identifiers.Integer, new Variant(32)},
  82. {"Int16", Identifiers.Int16, new Variant((short) 16)},
  83. {"Int32", Identifiers.Int32, new Variant(32)},
  84. {"Int64", Identifiers.Int64, new Variant(64L)},
  85. {"UInteger", Identifiers.UInteger, new Variant(uint(32))},
  86. {"UInt16", Identifiers.UInt16, new Variant(ushort(16))},
  87. {"UInt32", Identifiers.UInt32, new Variant(uint(32))},
  88. {"UInt64", Identifiers.UInt64, new Variant(ulong(64L))},
  89. {"Float", Identifiers.Float, new Variant(3.14f)},
  90. {"Double", Identifiers.Double, new Variant(3.14d)},
  91. {"String", Identifiers.String, new Variant("string value")},
  92. {"DateTime", Identifiers.DateTime, new Variant(DateTime.now())},
  93. {"Guid", Identifiers.Guid, new Variant(UUID.randomUUID())},
  94. {"ByteString", Identifiers.ByteString, new Variant(new ByteString(new byte[]{0x01, 0x02, 0x03, 0x04}))},
  95. {"XmlElement", Identifiers.XmlElement, new Variant(new XmlElement("<a>hello</a>"))},
  96. {"LocalizedText", Identifiers.LocalizedText, new Variant(LocalizedText.english("localized text"))},
  97. {"QualifiedName", Identifiers.QualifiedName, new Variant(new QualifiedName(1234, "defg"))},
  98. {"NodeId", Identifiers.NodeId, new Variant(new NodeId(1234, "abcd"))},
  99. {"Duration", Identifiers.Duration, new Variant(1.0)},
  100. {"UtcTime", Identifiers.UtcTime, new Variant(DateTime.now())},
  101. };
  102. private static final Object[][] STATIC_ARRAY_NODES = new Object[][]{
  103. {"BooleanArray", Identifiers.Boolean, false},
  104. {"ByteArray", Identifiers.Byte, ubyte(0)},
  105. {"SByteArray", Identifiers.SByte, (byte) 0x00},
  106. {"Int16Array", Identifiers.Int16, (short) 16},
  107. {"Int32Array", Identifiers.Int32, 32},
  108. {"Int64Array", Identifiers.Int64, 64L},
  109. {"UInt16Array", Identifiers.UInt16, ushort(16)},
  110. {"UInt32Array", Identifiers.UInt32, uint(32)},
  111. {"UInt64Array", Identifiers.UInt64, ulong(64L)},
  112. {"FloatArray", Identifiers.Float, 3.14f},
  113. {"DoubleArray", Identifiers.Double, 3.14d},
  114. {"StringArray", Identifiers.String, "string value"},
  115. {"DateTimeArray", Identifiers.DateTime, DateTime.now()},
  116. {"GuidArray", Identifiers.Guid, UUID.randomUUID()},
  117. {"ByteStringArray", Identifiers.ByteString, new ByteString(new byte[]{0x01, 0x02, 0x03, 0x04})},
  118. {"XmlElementArray", Identifiers.XmlElement, new XmlElement("<a>hello</a>")},
  119. {"LocalizedTextArray", Identifiers.LocalizedText, LocalizedText.english("localized text")},
  120. {"QualifiedNameArray", Identifiers.QualifiedName, new QualifiedName(1234, "defg")},
  121. {"NodeIdArray", Identifiers.NodeId, new NodeId(1234, "abcd")}
  122. };
  123. private final Logger logger = LoggerFactory.getLogger(getClass());
  124. private final Random random = new Random();
  125. private final SubscriptionModel subscriptionModel;
  126. private final NodeFactory nodeFactory;
  127. private final OpcUaServer server;
  128. private final UShort namespaceIndex;
  129. public MyNamespace(OpcUaServer server, UShort namespaceIndex) {
  130. this.server = server;
  131. this.namespaceIndex = namespaceIndex;
  132. subscriptionModel = new SubscriptionModel(server, this);
  133. nodeFactory = new NodeFactory(
  134. server.getNodeMap(),
  135. server.getObjectTypeManager(),
  136. server.getVariableTypeManager()
  137. );
  138. try {
  139. // Create a "HelloWorld" folder and add it to the node manager
  140. NodeId folderNodeId = new NodeId(namespaceIndex, "HelloWorld");
  141. UaFolderNode folderNode = new UaFolderNode(
  142. server.getNodeMap(),
  143. folderNodeId,
  144. new QualifiedName(namespaceIndex, "HelloWorld"),
  145. LocalizedText.english("HelloWorld")
  146. );
  147. server.getNodeMap().addNode(folderNode);
  148. // Make sure our new folder shows up under the server's Objects folder
  149. server.getUaNamespace().addReference(
  150. Identifiers.ObjectsFolder,
  151. Identifiers.Organizes,
  152. true,
  153. folderNodeId.expanded(),
  154. NodeClass.Object
  155. );
  156. // Add the rest of the nodes
  157. addVariableNodes(folderNode);
  158. addMethodNode(folderNode);
  159. addCustomDataTypeVariable(folderNode);
  160. addCustomObjectTypeAndInstance(folderNode);
  161. } catch (UaException e) {
  162. logger.error("Error adding nodes: {}", e.getMessage(), e);
  163. }
  164. }
  165. @Override
  166. public UShort getNamespaceIndex() {
  167. return namespaceIndex;
  168. }
  169. @Override
  170. public String getNamespaceUri() {
  171. return NAMESPACE_URI;
  172. }
  173. private void addVariableNodes(UaFolderNode rootNode) {
  174. addArrayNodes(rootNode);
  175. addScalarNodes(rootNode);
  176. addAdminReadableNodes(rootNode);
  177. addAdminWritableNodes(rootNode);
  178. addDynamicNodes(rootNode);
  179. addDataAccessNodes(rootNode);
  180. addWriteOnlyNodes(rootNode);
  181. }
  182. private void addArrayNodes(UaFolderNode rootNode) {
  183. UaFolderNode arrayTypesFolder = new UaFolderNode(
  184. server.getNodeMap(),
  185. new NodeId(namespaceIndex, "HelloWorld/ArrayTypes"),
  186. new QualifiedName(namespaceIndex, "ArrayTypes"),
  187. LocalizedText.english("ArrayTypes")
  188. );
  189. server.getNodeMap().addNode(arrayTypesFolder);
  190. rootNode.addOrganizes(arrayTypesFolder);
  191. for (Object[] os : STATIC_ARRAY_NODES) {
  192. String name = (String) os[0];
  193. NodeId typeId = (NodeId) os[1];
  194. Object value = os[2];
  195. Object array = Array.newInstance(value.getClass(), 5);
  196. for (int i = 0; i < 5; i++) {
  197. Array.set(array, i, value);
  198. }
  199. Variant variant = new Variant(array);
  200. UaVariableNode node = new UaVariableNode.UaVariableNodeBuilder(server.getNodeMap())
  201. .setNodeId(new NodeId(namespaceIndex, "HelloWorld/ArrayTypes/" + name))
  202. .setAccessLevel(ubyte(AccessLevel.getMask(AccessLevel.READ_WRITE)))
  203. .setUserAccessLevel(ubyte(AccessLevel.getMask(AccessLevel.READ_WRITE)))
  204. .setBrowseName(new QualifiedName(namespaceIndex, name))
  205. .setDisplayName(LocalizedText.english(name))
  206. .setDataType(typeId)
  207. .setTypeDefinition(Identifiers.BaseDataVariableType)
  208. .setValueRank(ValueRank.OneDimension.getValue())
  209. .setArrayDimensions(new UInteger[]{uint(0)})
  210. .build();
  211. node.setValue(new DataValue(variant));
  212. node.setAttributeDelegate(new ValueLoggingDelegate());
  213. server.getNodeMap().addNode(node);
  214. arrayTypesFolder.addOrganizes(node);
  215. }
  216. }
  217. private void addScalarNodes(UaFolderNode rootNode) {
  218. UaFolderNode scalarTypesFolder = new UaFolderNode(
  219. server.getNodeMap(),
  220. new NodeId(namespaceIndex, "HelloWorld/ScalarTypes"),
  221. new QualifiedName(namespaceIndex, "ScalarTypes"),
  222. LocalizedText.english("ScalarTypes")
  223. );
  224. server.getNodeMap().addNode(scalarTypesFolder);
  225. rootNode.addOrganizes(scalarTypesFolder);
  226. for (Object[] os : STATIC_SCALAR_NODES) {
  227. String name = (String) os[0];
  228. NodeId typeId = (NodeId) os[1];
  229. Variant variant = (Variant) os[2];
  230. UaVariableNode node = new UaVariableNode.UaVariableNodeBuilder(server.getNodeMap())
  231. .setNodeId(new NodeId(namespaceIndex, "HelloWorld/ScalarTypes/" + name))
  232. .setAccessLevel(ubyte(AccessLevel.getMask(AccessLevel.READ_WRITE)))
  233. .setUserAccessLevel(ubyte(AccessLevel.getMask(AccessLevel.READ_WRITE)))
  234. .setBrowseName(new QualifiedName(namespaceIndex, name))
  235. .setDisplayName(LocalizedText.english(name))
  236. .setDataType(typeId)
  237. .setTypeDefinition(Identifiers.BaseDataVariableType)
  238. .build();
  239. node.setValue(new DataValue(variant));
  240. node.setAttributeDelegate(new ValueLoggingDelegate());
  241. server.getNodeMap().addNode(node);
  242. scalarTypesFolder.addOrganizes(node);
  243. }
  244. }
  245. private void addWriteOnlyNodes(UaFolderNode rootNode) {
  246. UaFolderNode writeOnlyFolder = new UaFolderNode(
  247. server.getNodeMap(),
  248. new NodeId(namespaceIndex, "HelloWorld/WriteOnly"),
  249. new QualifiedName(namespaceIndex, "WriteOnly"),
  250. LocalizedText.english("WriteOnly")
  251. );
  252. server.getNodeMap().addNode(writeOnlyFolder);
  253. rootNode.addOrganizes(writeOnlyFolder);
  254. String name = "String";
  255. UaVariableNode node = new UaVariableNode.UaVariableNodeBuilder(server.getNodeMap())
  256. .setNodeId(new NodeId(namespaceIndex, "HelloWorld/WriteOnly/" + name))
  257. .setAccessLevel(ubyte(AccessLevel.getMask(AccessLevel.WRITE_ONLY)))
  258. .setUserAccessLevel(ubyte(AccessLevel.getMask(AccessLevel.WRITE_ONLY)))
  259. .setBrowseName(new QualifiedName(namespaceIndex, name))
  260. .setDisplayName(LocalizedText.english(name))
  261. .setDataType(Identifiers.String)
  262. .setTypeDefinition(Identifiers.BaseDataVariableType)
  263. .build();
  264. node.setValue(new DataValue(new Variant("can't read this")));
  265. server.getNodeMap().addNode(node);
  266. writeOnlyFolder.addOrganizes(node);
  267. }
  268. private void addAdminReadableNodes(UaFolderNode rootNode) {
  269. UaFolderNode adminFolder = new UaFolderNode(
  270. server.getNodeMap(),
  271. new NodeId(namespaceIndex, "HelloWorld/OnlyAdminCanRead"),
  272. new QualifiedName(namespaceIndex, "OnlyAdminCanRead"),
  273. LocalizedText.english("OnlyAdminCanRead")
  274. );
  275. server.getNodeMap().addNode(adminFolder);
  276. rootNode.addOrganizes(adminFolder);
  277. String name = "String";
  278. UaVariableNode node = new UaVariableNode.UaVariableNodeBuilder(server.getNodeMap())
  279. .setNodeId(new NodeId(namespaceIndex, "HelloWorld/OnlyAdminCanRead/" + name))
  280. .setAccessLevel(ubyte(AccessLevel.getMask(AccessLevel.READ_WRITE)))
  281. .setBrowseName(new QualifiedName(namespaceIndex, name))
  282. .setDisplayName(LocalizedText.english(name))
  283. .setDataType(Identifiers.String)
  284. .setTypeDefinition(Identifiers.BaseDataVariableType)
  285. .build();
  286. node.setValue(new DataValue(new Variant("shh... don't tell the lusers")));
  287. node.setAttributeDelegate(new RestrictedAccessDelegate(identity -> {
  288. if ("admin".equals(identity)) {
  289. return AccessLevel.READ_WRITE;
  290. } else {
  291. return AccessLevel.NONE;
  292. }
  293. }));
  294. server.getNodeMap().addNode(node);
  295. adminFolder.addOrganizes(node);
  296. }
  297. private void addAdminWritableNodes(UaFolderNode rootNode) {
  298. UaFolderNode adminFolder = new UaFolderNode(
  299. server.getNodeMap(),
  300. new NodeId(namespaceIndex, "HelloWorld/OnlyAdminCanWrite"),
  301. new QualifiedName(namespaceIndex, "OnlyAdminCanWrite"),
  302. LocalizedText.english("OnlyAdminCanWrite")
  303. );
  304. server.getNodeMap().addNode(adminFolder);
  305. rootNode.addOrganizes(adminFolder);
  306. String name = "String";
  307. UaVariableNode node = new UaVariableNode.UaVariableNodeBuilder(server.getNodeMap())
  308. .setNodeId(new NodeId(namespaceIndex, "HelloWorld/OnlyAdminCanWrite/" + name))
  309. .setAccessLevel(ubyte(AccessLevel.getMask(AccessLevel.READ_WRITE)))
  310. .setBrowseName(new QualifiedName(namespaceIndex, name))
  311. .setDisplayName(LocalizedText.english(name))
  312. .setDataType(Identifiers.String)
  313. .setTypeDefinition(Identifiers.BaseDataVariableType)
  314. .build();
  315. node.setValue(new DataValue(new Variant("admin was here")));
  316. node.setAttributeDelegate(new RestrictedAccessDelegate(identity -> {
  317. if ("admin".equals(identity)) {
  318. return AccessLevel.READ_WRITE;
  319. } else {
  320. return AccessLevel.READ_ONLY;
  321. }
  322. }));
  323. server.getNodeMap().addNode(node);
  324. adminFolder.addOrganizes(node);
  325. }
  326. private void addDynamicNodes(UaFolderNode rootNode) {
  327. UaFolderNode dynamicFolder = new UaFolderNode(
  328. server.getNodeMap(),
  329. new NodeId(namespaceIndex, "HelloWorld/Dynamic"),
  330. new QualifiedName(namespaceIndex, "Dynamic"),
  331. LocalizedText.english("Dynamic")
  332. );
  333. server.getNodeMap().addNode(dynamicFolder);
  334. rootNode.addOrganizes(dynamicFolder);
  335. // Dynamic Boolean
  336. {
  337. String name = "Boolean";
  338. NodeId typeId = Identifiers.Boolean;
  339. Variant variant = new Variant(false);
  340. UaVariableNode node = new UaVariableNode.UaVariableNodeBuilder(server.getNodeMap())
  341. .setNodeId(new NodeId(namespaceIndex, "HelloWorld/Dynamic/" + name))
  342. .setAccessLevel(ubyte(AccessLevel.getMask(AccessLevel.READ_WRITE)))
  343. .setBrowseName(new QualifiedName(namespaceIndex, name))
  344. .setDisplayName(LocalizedText.english(name))
  345. .setDataType(typeId)
  346. .setTypeDefinition(Identifiers.BaseDataVariableType)
  347. .build();
  348. node.setValue(new DataValue(variant));
  349. AttributeDelegate delegate = AttributeDelegateChain.create(
  350. new AttributeDelegate() {
  351. @Override
  352. public DataValue getValue(AttributeContext context, VariableNode node) throws UaException {
  353. return new DataValue(new Variant(random.nextBoolean()));
  354. }
  355. },
  356. ValueLoggingDelegate::new
  357. );
  358. node.setAttributeDelegate(delegate);
  359. server.getNodeMap().addNode(node);
  360. dynamicFolder.addOrganizes(node);
  361. }
  362. // Dynamic Int32
  363. {
  364. String name = "Int32";
  365. NodeId typeId = Identifiers.Int32;
  366. Variant variant = new Variant(0);
  367. UaVariableNode node = new UaVariableNode.UaVariableNodeBuilder(server.getNodeMap())
  368. .setNodeId(new NodeId(namespaceIndex, "HelloWorld/Dynamic/" + name))
  369. .setAccessLevel(ubyte(AccessLevel.getMask(AccessLevel.READ_WRITE)))
  370. .setBrowseName(new QualifiedName(namespaceIndex, name))
  371. .setDisplayName(LocalizedText.english(name))
  372. .setDataType(typeId)
  373. .setTypeDefinition(Identifiers.BaseDataVariableType)
  374. .build();
  375. node.setValue(new DataValue(variant));
  376. AttributeDelegate delegate = AttributeDelegateChain.create(
  377. new AttributeDelegate() {
  378. @Override
  379. public DataValue getValue(AttributeContext context, VariableNode node) throws UaException {
  380. return new DataValue(new Variant(random.nextInt()));
  381. }
  382. },
  383. ValueLoggingDelegate::new
  384. );
  385. node.setAttributeDelegate(delegate);
  386. server.getNodeMap().addNode(node);
  387. dynamicFolder.addOrganizes(node);
  388. }
  389. // Dynamic Double
  390. {
  391. String name = "Double";
  392. NodeId typeId = Identifiers.Double;
  393. Variant variant = new Variant(0.0);
  394. UaVariableNode node = new UaVariableNode.UaVariableNodeBuilder(server.getNodeMap())
  395. .setNodeId(new NodeId(namespaceIndex, "HelloWorld/Dynamic/" + name))
  396. .setAccessLevel(ubyte(AccessLevel.getMask(AccessLevel.READ_WRITE)))
  397. .setBrowseName(new QualifiedName(namespaceIndex, name))
  398. .setDisplayName(LocalizedText.english(name))
  399. .setDataType(typeId)
  400. .setTypeDefinition(Identifiers.BaseDataVariableType)
  401. .build();
  402. node.setValue(new DataValue(variant));
  403. AttributeDelegate delegate = AttributeDelegateChain.create(
  404. new AttributeDelegate() {
  405. @Override
  406. public DataValue getValue(AttributeContext context, VariableNode node) throws UaException {
  407. return new DataValue(new Variant(random.nextDouble()));
  408. }
  409. },
  410. ValueLoggingDelegate::new
  411. );
  412. node.setAttributeDelegate(delegate);
  413. server.getNodeMap().addNode(node);
  414. dynamicFolder.addOrganizes(node);
  415. }
  416. }
  417. private void addDataAccessNodes(UaFolderNode rootNode) {
  418. // DataAccess folder
  419. UaFolderNode dataAccessFolder = new UaFolderNode(
  420. server.getNodeMap(),
  421. new NodeId(namespaceIndex, "HelloWorld/DataAccess"),
  422. new QualifiedName(namespaceIndex, "DataAccess"),
  423. LocalizedText.english("DataAccess")
  424. );
  425. server.getNodeMap().addNode(dataAccessFolder);
  426. rootNode.addOrganizes(dataAccessFolder);
  427. // AnalogItemType node
  428. AnalogItemNode node = nodeFactory.createVariable(
  429. new NodeId(namespaceIndex, "HelloWorld/DataAccess/AnalogValue"),
  430. new QualifiedName(namespaceIndex, "AnalogValue"),
  431. LocalizedText.english("AnalogValue"),
  432. Identifiers.AnalogItemType,
  433. AnalogItemNode.class
  434. );
  435. node.setDataType(Identifiers.Double);
  436. node.setValue(new DataValue(new Variant(3.14d)));
  437. node.setEURange(new Range(0.0, 100.0));
  438. server.getNodeMap().addNode(node);
  439. dataAccessFolder.addOrganizes(node);
  440. }
  441. private void addMethodNode(UaFolderNode folderNode) {
  442. UaMethodNode methodNode = UaMethodNode.builder(server.getNodeMap())
  443. .setNodeId(new NodeId(namespaceIndex, "HelloWorld/sqrt(x)"))
  444. .setBrowseName(new QualifiedName(namespaceIndex, "sqrt(x)"))
  445. .setDisplayName(new LocalizedText(null, "sqrt(x)"))
  446. .setDescription(
  447. LocalizedText.english("Returns the correctly rounded positive square root of a double value."))
  448. .build();
  449. try {
  450. AnnotationBasedInvocationHandler invocationHandler =
  451. AnnotationBasedInvocationHandler.fromAnnotatedObject(
  452. server.getNodeMap(), new SqrtMethod());
  453. methodNode.setProperty(UaMethodNode.InputArguments, invocationHandler.getInputArguments());
  454. methodNode.setProperty(UaMethodNode.OutputArguments, invocationHandler.getOutputArguments());
  455. methodNode.setInvocationHandler(invocationHandler);
  456. server.getNodeMap().addNode(methodNode);
  457. folderNode.addReference(new Reference(
  458. folderNode.getNodeId(),
  459. Identifiers.HasComponent,
  460. methodNode.getNodeId().expanded(),
  461. methodNode.getNodeClass(),
  462. true
  463. ));
  464. methodNode.addReference(new Reference(
  465. methodNode.getNodeId(),
  466. Identifiers.HasComponent,
  467. folderNode.getNodeId().expanded(),
  468. folderNode.getNodeClass(),
  469. false
  470. ));
  471. } catch (Exception e) {
  472. logger.error("Error creating sqrt() method.", e);
  473. }
  474. }
  475. private void addCustomObjectTypeAndInstance(UaFolderNode rootFolder) throws UaException {
  476. // Define a new ObjectType called "MyObjectType".
  477. UaObjectTypeNode objectTypeNode = UaObjectTypeNode.builder(server.getNodeMap())
  478. .setNodeId(new NodeId(namespaceIndex, "ObjectTypes/MyObjectType"))
  479. .setBrowseName(new QualifiedName(namespaceIndex, "MyObjectType"))
  480. .setDisplayName(LocalizedText.english("MyObjectType"))
  481. .setIsAbstract(false)
  482. .build();
  483. // "Foo" and "Bar" are members. These nodes are what are called "instance declarations" by the spec.
  484. UaVariableNode foo = UaVariableNode.builder(server.getNodeMap())
  485. .setNodeId(new NodeId(namespaceIndex, "ObjectTypes/MyObjectType.Foo"))
  486. .setAccessLevel(ubyte(AccessLevel.getMask(AccessLevel.READ_WRITE)))
  487. .setBrowseName(new QualifiedName(namespaceIndex, "Foo"))
  488. .setDisplayName(LocalizedText.english("Foo"))
  489. .setDataType(Identifiers.Int16)
  490. .setTypeDefinition(Identifiers.BaseDataVariableType)
  491. .build();
  492. foo.setValue(new DataValue(new Variant(0)));
  493. objectTypeNode.addComponent(foo);
  494. UaVariableNode bar = UaVariableNode.builder(server.getNodeMap())
  495. .setNodeId(new NodeId(namespaceIndex, "ObjectTypes/MyObjectType.Bar"))
  496. .setAccessLevel(ubyte(AccessLevel.getMask(AccessLevel.READ_WRITE)))
  497. .setBrowseName(new QualifiedName(namespaceIndex, "Bar"))
  498. .setDisplayName(LocalizedText.english("Bar"))
  499. .setDataType(Identifiers.String)
  500. .setTypeDefinition(Identifiers.BaseDataVariableType)
  501. .build();
  502. bar.setValue(new DataValue(new Variant("bar")));
  503. objectTypeNode.addComponent(bar);
  504. // Tell the ObjectTypeManager about our new type.
  505. // This let's us use NodeFactory to instantiate instances of the type.
  506. server.getObjectTypeManager().registerObjectType(
  507. objectTypeNode.getNodeId(),
  508. UaObjectNode.class,
  509. UaObjectNode::new
  510. );
  511. // Add our ObjectTypeNode as a subtype of BaseObjectType.
  512. server.getUaNamespace().addReference(
  513. Identifiers.BaseObjectType,
  514. Identifiers.HasSubtype,
  515. true,
  516. objectTypeNode.getNodeId().expanded(),
  517. NodeClass.ObjectType
  518. );
  519. // Add the inverse SubtypeOf relationship.
  520. objectTypeNode.addReference(new Reference(
  521. objectTypeNode.getNodeId(),
  522. Identifiers.HasSubtype,
  523. Identifiers.BaseObjectType.expanded(),
  524. NodeClass.ObjectType,
  525. false
  526. ));
  527. // Add it into the address space.
  528. server.getNodeMap().addNode(objectTypeNode);
  529. // Use NodeFactory to create instance of MyObjectType called "MyObject".
  530. // NodeFactory takes care of recursively instantiating MyObject member nodes
  531. // as well as adding all nodes to the address space.
  532. UaObjectNode myObject = nodeFactory.createObject(
  533. new NodeId(namespaceIndex, "HelloWorld/MyObject"),
  534. new QualifiedName(namespaceIndex, "MyObject"),
  535. LocalizedText.english("MyObject"),
  536. objectTypeNode.getNodeId()
  537. );
  538. // Add forward and inverse references from the root folder.
  539. rootFolder.addOrganizes(myObject);
  540. myObject.addReference(new Reference(
  541. myObject.getNodeId(),
  542. Identifiers.Organizes,
  543. rootFolder.getNodeId().expanded(),
  544. rootFolder.getNodeClass(),
  545. false
  546. ));
  547. }
  548. private void addCustomDataTypeVariable(UaFolderNode rootFolder) {
  549. // add a custom DataTypeNode as a subtype of the built-in Structure DataTypeNode
  550. NodeId dataTypeId = new NodeId(namespaceIndex, "DataType.CustomDataType");
  551. UaDataTypeNode dataTypeNode = new UaDataTypeNode(
  552. server.getNodeMap(),
  553. dataTypeId,
  554. new QualifiedName(namespaceIndex, "CustomDataType"),
  555. LocalizedText.english("CustomDataType"),
  556. LocalizedText.english("CustomDataType"),
  557. uint(0),
  558. uint(0),
  559. false
  560. );
  561. // Inverse ref to Structure
  562. dataTypeNode.addReference(new Reference(
  563. dataTypeId,
  564. Identifiers.HasSubtype,
  565. Identifiers.Structure.expanded(),
  566. NodeClass.DataType,
  567. false
  568. ));
  569. // Forward ref from Structure
  570. Optional<UaDataTypeNode> structureDataTypeNode = server.getNodeMap()
  571. .getNode(Identifiers.Structure)
  572. .map(UaDataTypeNode.class::cast);
  573. structureDataTypeNode.ifPresent(node ->
  574. node.addReference(new Reference(
  575. node.getNodeId(),
  576. Identifiers.HasSubtype,
  577. dataTypeId.expanded(),
  578. NodeClass.DataType,
  579. true
  580. ))
  581. );
  582. // Create a dictionary, binaryEncodingId, and register the codec under that id
  583. OpcUaBinaryDataTypeDictionary dictionary = new OpcUaBinaryDataTypeDictionary(
  584. "urn:eclipse:milo:example:custom-data-type"
  585. );
  586. NodeId binaryEncodingId = new NodeId(namespaceIndex, "DataType.CustomDataType.BinaryEncoding");
  587. dictionary.registerStructCodec(
  588. new CustomDataType.Codec().asBinaryCodec(),
  589. "CustomDataType",
  590. binaryEncodingId
  591. );
  592. // Register dictionary with the shared DataTypeManager instance
  593. OpcUaDataTypeManager.getInstance().registerTypeDictionary(dictionary);
  594. UaVariableNode customDataTypeVariable = UaVariableNode.builder(server.getNodeMap())
  595. .setNodeId(new NodeId(namespaceIndex, "HelloWorld/CustomDataTypeVariable"))
  596. .setAccessLevel(ubyte(AccessLevel.getMask(AccessLevel.READ_WRITE)))
  597. .setUserAccessLevel(ubyte(AccessLevel.getMask(AccessLevel.READ_WRITE)))
  598. .setBrowseName(new QualifiedName(namespaceIndex, "CustomDataTypeVariable"))
  599. .setDisplayName(LocalizedText.english("CustomDataTypeVariable"))
  600. .setDataType(dataTypeId)
  601. .setTypeDefinition(Identifiers.BaseDataVariableType)
  602. .build();
  603. CustomDataType value = new CustomDataType(
  604. "foo",
  605. uint(42),
  606. true
  607. );
  608. ExtensionObject xo = ExtensionObject.encode(value, binaryEncodingId);
  609. customDataTypeVariable.setValue(new DataValue(new Variant(xo)));
  610. rootFolder.addOrganizes(customDataTypeVariable);
  611. customDataTypeVariable.addReference(new Reference(
  612. customDataTypeVariable.getNodeId(),
  613. Identifiers.Organizes,
  614. rootFolder.getNodeId().expanded(),
  615. rootFolder.getNodeClass(),
  616. false
  617. ));
  618. }
  619. @Override
  620. public CompletableFuture<List<Reference>> browse(AccessContext context, NodeId nodeId) {
  621. ServerNode node = server.getNodeMap().get(nodeId);
  622. if (node != null) {
  623. return CompletableFuture.completedFuture(node.getReferences());
  624. } else {
  625. return FutureUtils.failedFuture(new UaException(StatusCodes.Bad_NodeIdUnknown));
  626. }
  627. }
  628. @Override
  629. public void read(
  630. ReadContext context,
  631. Double maxAge,
  632. TimestampsToReturn timestamps,
  633. List<ReadValueId> readValueIds) {
  634. List<DataValue> results = Lists.newArrayListWithCapacity(readValueIds.size());
  635. for (ReadValueId readValueId : readValueIds) {
  636. ServerNode node = server.getNodeMap().get(readValueId.getNodeId());
  637. if (node != null) {
  638. DataValue value = node.readAttribute(
  639. new AttributeContext(context),
  640. readValueId.getAttributeId(),
  641. timestamps,
  642. readValueId.getIndexRange(),
  643. readValueId.getDataEncoding()
  644. );
  645. results.add(value);
  646. } else {
  647. results.add(new DataValue(StatusCodes.Bad_NodeIdUnknown));
  648. }
  649. }
  650. context.complete(results);
  651. }
  652. @Override
  653. public void write(WriteContext context, List<WriteValue> writeValues) {
  654. List<StatusCode> results = Lists.newArrayListWithCapacity(writeValues.size());
  655. for (WriteValue writeValue : writeValues) {
  656. ServerNode node = server.getNodeMap().get(writeValue.getNodeId());
  657. if (node != null) {
  658. try {
  659. node.writeAttribute(
  660. new AttributeContext(context),
  661. writeValue.getAttributeId(),
  662. writeValue.getValue(),
  663. writeValue.getIndexRange()
  664. );
  665. results.add(StatusCode.GOOD);
  666. logger.info(
  667. "Wrote value {} to {} attribute of {}",
  668. writeValue.getValue().getValue(),
  669. AttributeId.from(writeValue.getAttributeId()).map(Object::toString).orElse("unknown"),
  670. node.getNodeId());
  671. } catch (UaException e) {
  672. logger.error("Unable to write value={}", writeValue.getValue(), e);
  673. results.add(e.getStatusCode());
  674. }
  675. } else {
  676. results.add(new StatusCode(StatusCodes.Bad_NodeIdUnknown));
  677. }
  678. }
  679. context.complete(results);
  680. }
  681. @Override
  682. public void onDataItemsCreated(List<DataItem> dataItems) {
  683. subscriptionModel.onDataItemsCreated(dataItems);
  684. }
  685. @Override
  686. public void onDataItemsModified(List<DataItem> dataItems) {
  687. subscriptionModel.onDataItemsModified(dataItems);
  688. }
  689. @Override
  690. public void onDataItemsDeleted(List<DataItem> dataItems) {
  691. subscriptionModel.onDataItemsDeleted(dataItems);
  692. }
  693. @Override
  694. public void onMonitoringModeChanged(List<MonitoredItem> monitoredItems) {
  695. subscriptionModel.onMonitoringModeChanged(monitoredItems);
  696. }
  697. @Override
  698. public Optional<MethodInvocationHandler> getInvocationHandler(NodeId methodId) {
  699. Optional<ServerNode> node = server.getNodeMap().getNode(methodId);
  700. return node.flatMap(n -> {
  701. if (n instanceof UaMethodNode) {
  702. return ((UaMethodNode) n).getInvocationHandler();
  703. } else {
  704. return Optional.empty();
  705. }
  706. });
  707. }
  708. }