diff --git a/src/PluginNodes/OpaqueAndNodeIdPluginNode.cs b/src/PluginNodes/OpaqueAndNodeIdPluginNode.cs
new file mode 100644
index 00000000..778e15cb
--- /dev/null
+++ b/src/PluginNodes/OpaqueAndNodeIdPluginNode.cs
@@ -0,0 +1,174 @@
+namespace OpcPlc.PluginNodes;
+
+using Microsoft.Extensions.Logging;
+using Opc.Ua;
+using OpcPlc.Helpers;
+using OpcPlc.PluginNodes.Models;
+using System;
+using System.Collections.Generic;
+
+///
+/// Node with an opaque identifier (free-format byte string that might or might not be human interpretable).
+/// as well as nodes of type NodeId and ExpandedNodeId with IdType of String, Numeric, Opaque, and Guid.
+///
+public class OpaqueAndNodeIdPluginNode(TimeService timeService, ILogger logger) : PluginNodeBase(timeService, logger), IPluginNodes
+{
+ private PlcNodeManager _plcNodeManager;
+ private SimulatedVariableNode _node;
+
+ public void AddOptions(Mono.Options.OptionSet optionSet)
+ {
+ // on|opaquenode
+ // Add node with an opaque identifier.
+ // Enabled by default.
+ }
+
+ public void AddToAddressSpace(FolderState telemetryFolder, FolderState methodsFolder, PlcNodeManager plcNodeManager)
+ {
+ _plcNodeManager = plcNodeManager;
+
+ FolderState folder = _plcNodeManager.CreateFolder(
+ telemetryFolder,
+ path: "Special",
+ name: "Special",
+ NamespaceType.OpcPlcApplications);
+
+ AddNodes(folder);
+ }
+
+ public void StartSimulation()
+ {
+ _node.Start(value => value + 1, periodMs: 1000);
+ }
+
+ public void StopSimulation()
+ {
+ _node.Stop();
+ }
+
+ private void AddNodes(FolderState folder)
+ {
+ BaseDataVariableState variable = _plcNodeManager.CreateBaseVariable(
+ folder,
+ path: new byte[] { (byte)'a', (byte)'b', (byte)'c' },
+ name: "Opaque_abc",
+ new NodeId((uint)BuiltInType.UInt32),
+ ValueRanks.Scalar,
+ AccessLevels.CurrentReadOrWrite,
+ "Constantly increasing value",
+ NamespaceType.OpcPlcApplications,
+ defaultValue: (uint)0);
+
+ _node = _plcNodeManager.CreateVariableNode(variable);
+
+ BaseDataVariableState stringNodeIdVariable = _plcNodeManager.CreateBaseVariable(
+ folder,
+ "ScalarStaticNodeIdString",
+ "ScalarStaticNodeIdString",
+ new NodeId("ScalarStaticNodeIdString", 3),
+ ValueRanks.Scalar,
+ AccessLevels.CurrentReadOrWrite,
+ "String representation of the NodeId",
+ NamespaceType.OpcPlcApplications,
+ defaultValue: new NodeId("this is a string node id", 3)
+ );
+
+ BaseDataVariableState numericNodeIdVariable = _plcNodeManager.CreateBaseVariable(
+ folder,
+ "ScalarStaticNodeIdNumeric",
+ "ScalarStaticNodeIdNumeric",
+ new NodeId("ScalarStaticNodeIdNumeric", 3),
+ ValueRanks.Scalar,
+ AccessLevels.CurrentReadOrWrite,
+ "UInt32 representation of the NodeId",
+ NamespaceType.OpcPlcApplications,
+ defaultValue: new NodeId(42, 3)
+ );
+
+ BaseDataVariableState opaqueNodeIdVariable = _plcNodeManager.CreateBaseVariable(
+ folder,
+ "ScalarStaticNodeIdOpaque",
+ "ScalarStaticNodeIdOpaque",
+ new NodeId("ScalarStaticNodeIdOpaque", 3),
+ ValueRanks.Scalar,
+ AccessLevels.CurrentReadOrWrite,
+ "Opaque representation of the NodeId",
+ NamespaceType.OpcPlcApplications,
+ defaultValue: new NodeId(new byte[] { 0x01, 0x02, 0x03, 0x04 }, 3)
+ );
+
+ BaseDataVariableState guidNodeIdVariable = _plcNodeManager.CreateBaseVariable(
+ folder,
+ "ScalarStaticNodeIdGuid",
+ "ScalarStaticNodeIdGuid",
+ new NodeId("ScalarStaticNodeIdGuid", 3),
+ ValueRanks.Scalar,
+ AccessLevels.CurrentReadOrWrite,
+ "Guid representation of the NodeId",
+ NamespaceType.OpcPlcApplications,
+ defaultValue: new NodeId(Guid.NewGuid(), 3)
+ );
+
+ BaseDataVariableState stringExpandedNodeIdVariable = _plcNodeManager.CreateBaseVariable(
+ folder,
+ "ScalarStaticExpandedNodeIdString",
+ "ScalarStaticExpandedNodeIdString",
+ new NodeId("ScalarStaticExpandedNodeIdString", 3),
+ ValueRanks.Scalar,
+ AccessLevels.CurrentReadOrWrite,
+ "String representation of the ExpandedNodeId",
+ NamespaceType.OpcPlcApplications,
+ defaultValue: new ExpandedNodeId("this is a string expanded node id", 3, OpcPlc.Namespaces.OpcPlcApplications, 0)
+ );
+
+ BaseDataVariableState numericExpandedNodeIdVariable = _plcNodeManager.CreateBaseVariable(
+ folder,
+ "ScalarStaticExpandedNodeIdNumeric",
+ "ScalarStaticExpandedNodeIdNumeric",
+ new NodeId("ScalarStaticExpandedNodeIdNumeric", 3),
+ ValueRanks.Scalar,
+ AccessLevels.CurrentReadOrWrite,
+ "Numeric representation of the ExpandedNodeId",
+ NamespaceType.OpcPlcApplications,
+ defaultValue: new ExpandedNodeId(444u, 3, OpcPlc.Namespaces.OpcPlcApplications, 0)
+ );
+
+ BaseDataVariableState guidExpandedNodeIdVariable = _plcNodeManager.CreateBaseVariable(
+ folder,
+ "ScalarStaticExpandedNodeIdGuid",
+ "ScalarStaticExpandedNodeIdGuid",
+ new NodeId("ScalarStaticExpandedNodeIdGuid", 3),
+ ValueRanks.Scalar,
+ AccessLevels.CurrentReadOrWrite,
+ "Guid representation of the ExpandedNodeId",
+ NamespaceType.OpcPlcApplications,
+ defaultValue: new ExpandedNodeId(Guid.NewGuid(), 3, OpcPlc.Namespaces.OpcPlcApplications, 0)
+ );
+
+ BaseDataVariableState opaqueExpandedNodeIdVariable = _plcNodeManager.CreateBaseVariable(
+ folder,
+ "ScalarStaticExpandedNodeIdOpaque",
+ "ScalarStaticExpandedNodeIdOpaque",
+ new NodeId("ScalarStaticExpandedNodeIdOpaque", 3),
+ ValueRanks.Scalar,
+ AccessLevels.CurrentReadOrWrite,
+ "Opaque representation of the ExpandedNodeId",
+ NamespaceType.OpcPlcApplications,
+ defaultValue: new ExpandedNodeId(new byte[] { 0xCA, 0xFE}, 3, OpcPlc.Namespaces.OpcPlcApplications, 0)
+ );
+
+ // Add to node list for creation of pn.json.
+ Nodes = new List
+ {
+ PluginNodesHelper.GetNodeWithIntervals(variable.NodeId, _plcNodeManager),
+ PluginNodesHelper.GetNodeWithIntervals(stringNodeIdVariable.NodeId, _plcNodeManager),
+ PluginNodesHelper.GetNodeWithIntervals(numericNodeIdVariable.NodeId, _plcNodeManager),
+ PluginNodesHelper.GetNodeWithIntervals(opaqueNodeIdVariable.NodeId, _plcNodeManager),
+ PluginNodesHelper.GetNodeWithIntervals(guidNodeIdVariable.NodeId, _plcNodeManager),
+ PluginNodesHelper.GetNodeWithIntervals(stringExpandedNodeIdVariable.NodeId, _plcNodeManager),
+ PluginNodesHelper.GetNodeWithIntervals(numericExpandedNodeIdVariable.NodeId, _plcNodeManager),
+ PluginNodesHelper.GetNodeWithIntervals(guidExpandedNodeIdVariable.NodeId, _plcNodeManager),
+ PluginNodesHelper.GetNodeWithIntervals(opaqueExpandedNodeIdVariable.NodeId, _plcNodeManager),
+ };
+ }
+}
diff --git a/src/PluginNodes/OpaquePluginNode.cs b/src/PluginNodes/OpaquePluginNode.cs
deleted file mode 100644
index 8ec134b3..00000000
--- a/src/PluginNodes/OpaquePluginNode.cs
+++ /dev/null
@@ -1,68 +0,0 @@
-namespace OpcPlc.PluginNodes;
-
-using Microsoft.Extensions.Logging;
-using Opc.Ua;
-using OpcPlc.Helpers;
-using OpcPlc.PluginNodes.Models;
-using System.Collections.Generic;
-
-///
-/// Node with an opaque identifier (free-format byte string that might or might not be human interpretable).
-///
-public class OpaquePluginNode(TimeService timeService, ILogger logger) : PluginNodeBase(timeService, logger), IPluginNodes
-{
- private PlcNodeManager _plcNodeManager;
- private SimulatedVariableNode _node;
-
- public void AddOptions(Mono.Options.OptionSet optionSet)
- {
- // on|opaquenode
- // Add node with an opaque identifier.
- // Enabled by default.
- }
-
- public void AddToAddressSpace(FolderState telemetryFolder, FolderState methodsFolder, PlcNodeManager plcNodeManager)
- {
- _plcNodeManager = plcNodeManager;
-
- FolderState folder = _plcNodeManager.CreateFolder(
- telemetryFolder,
- path: "Special",
- name: "Special",
- NamespaceType.OpcPlcApplications);
-
- AddNodes(folder);
- }
-
- public void StartSimulation()
- {
- _node.Start(value => value + 1, periodMs: 1000);
- }
-
- public void StopSimulation()
- {
- _node.Stop();
- }
-
- private void AddNodes(FolderState folder)
- {
- BaseDataVariableState variable = _plcNodeManager.CreateBaseVariable(
- folder,
- path: new byte[] { (byte)'a', (byte)'b', (byte)'c' },
- name: "Opaque_abc",
- new NodeId((uint)BuiltInType.UInt32),
- ValueRanks.Scalar,
- AccessLevels.CurrentReadOrWrite,
- "Constantly increasing value",
- NamespaceType.OpcPlcApplications,
- defaultValue: (uint)0);
-
- _node = _plcNodeManager.CreateVariableNode(variable);
-
- // Add to node list for creation of pn.json.
- Nodes = new List
- {
- PluginNodesHelper.GetNodeWithIntervals(variable.NodeId, _plcNodeManager),
- };
- }
-}
diff --git a/tests/OpaqueAndNodeIdTests.cs b/tests/OpaqueAndNodeIdTests.cs
new file mode 100644
index 00000000..e15dbd4e
--- /dev/null
+++ b/tests/OpaqueAndNodeIdTests.cs
@@ -0,0 +1,67 @@
+namespace OpcPlc.Tests;
+
+using FluentAssertions;
+using NUnit.Framework;
+
+///
+/// Tests NodeIds of various IdTypes and ExpandedNodeIds.
+///
+[TestFixture]
+public class OpaqueAndNodeIdTests : SubscriptionTestsBase
+{
+
+ [Test]
+ public void TestNodeIdNodes()
+ {
+ var specialFolder = FindNode(ObjectsFolder, Namespaces.OpcPlcApplications, "OpcPlc", "Telemetry", "Special");
+ specialFolder.Should().NotBeNull();
+
+ var stringNodeId = FindNode(specialFolder, Namespaces.OpcPlcApplications, "ScalarStaticNodeIdString");
+ stringNodeId.Should().NotBeNull();
+ var stringNodeIdValue = ReadValue(stringNodeId);
+ stringNodeIdValue.Should().NotBeNull();
+ stringNodeIdValue.IdType.Should().Be(Opc.Ua.IdType.String);
+
+ var numericNodeId = FindNode(specialFolder, Namespaces.OpcPlcApplications, "ScalarStaticNodeIdNumeric");
+ numericNodeId.Should().NotBeNull();
+ var numericNodeIdValue = ReadValue(numericNodeId);
+ numericNodeIdValue.Should().NotBeNull();
+ numericNodeIdValue.IdType.Should().Be(Opc.Ua.IdType.Numeric);
+
+ var guidNodeId = FindNode(specialFolder, Namespaces.OpcPlcApplications, "ScalarStaticNodeIdGuid");
+ guidNodeId.Should().NotBeNull();
+ var guidNodeIdValue = ReadValue(guidNodeId);
+ guidNodeIdValue.Should().NotBeNull();
+ guidNodeIdValue.IdType.Should().Be(Opc.Ua.IdType.Guid);
+
+ var opaqueNodeId = FindNode(specialFolder, Namespaces.OpcPlcApplications, "ScalarStaticNodeIdOpaque");
+ opaqueNodeId.Should().NotBeNull();
+ var opaqueNodeIdValue = ReadValue(opaqueNodeId);
+ opaqueNodeIdValue.Should().NotBeNull();
+ opaqueNodeIdValue.IdType.Should().Be(Opc.Ua.IdType.Opaque);
+
+ var stringExpandedNodeId = FindNode(specialFolder, Namespaces.OpcPlcApplications, "ScalarStaticExpandedNodeIdString");
+ stringExpandedNodeId.Should().NotBeNull();
+ var stringExpandedNodeIdValue = ReadValue(stringExpandedNodeId);
+ stringExpandedNodeIdValue.Should().NotBeNull();
+ stringExpandedNodeIdValue.IdType.Should().Be(Opc.Ua.IdType.String);
+
+ var numericExpandedNodeId = FindNode(specialFolder, Namespaces.OpcPlcApplications, "ScalarStaticExpandedNodeIdNumeric");
+ numericExpandedNodeId.Should().NotBeNull();
+ var numericExpandedNodeIdValue = ReadValue(numericExpandedNodeId);
+ numericExpandedNodeIdValue.Should().NotBeNull();
+ numericExpandedNodeIdValue.IdType.Should().Be(Opc.Ua.IdType.Numeric);
+
+ var guidExpandedNodeId = FindNode(specialFolder, Namespaces.OpcPlcApplications, "ScalarStaticExpandedNodeIdGuid");
+ guidExpandedNodeId.Should().NotBeNull();
+ var guidExpandedNodeIdValue = ReadValue(guidExpandedNodeId);
+ guidExpandedNodeIdValue.Should().NotBeNull();
+ guidExpandedNodeIdValue.IdType.Should().Be(Opc.Ua.IdType.Guid);
+
+ var opaqueExpandedNodeId = FindNode(specialFolder, Namespaces.OpcPlcApplications, "ScalarStaticExpandedNodeIdOpaque");
+ opaqueExpandedNodeId.Should().NotBeNull();
+ var opaqueExpandedNodeIdValue = ReadValue(opaqueExpandedNodeId);
+ opaqueExpandedNodeIdValue.Should().NotBeNull();
+ opaqueExpandedNodeIdValue.IdType.Should().Be(Opc.Ua.IdType.Opaque);
+ }
+}