diff --git a/Stack/Opc.Ua.Types/Utils/TypeInfo.cs b/Stack/Opc.Ua.Types/Utils/TypeInfo.cs index c030ae612..d115a93fd 100644 --- a/Stack/Opc.Ua.Types/Utils/TypeInfo.cs +++ b/Stack/Opc.Ua.Types/Utils/TypeInfo.cs @@ -279,6 +279,38 @@ public static NodeId GetDataTypeId(Type type) } } + // Check if the type implements IEncodeable and has a specific TypeId + if (dataTypeId == DataTypeIds.Structure && + typeof(IEncodeable).GetTypeInfo().IsAssignableFrom(type.GetTypeInfo())) + { + // Try to create an instance and get its TypeId + // All well-known IEncodeable types have parameterless constructors and instance TypeId properties + try + { + var instance = Activator.CreateInstance(type) as IEncodeable; + if (instance?.TypeId != null) + { + return ExpandedNodeId.ToNodeId(instance.TypeId, null); + } + } + catch (MissingMethodException) + { + // Type doesn't have a parameterless constructor, fall back to Structure + } + catch (TargetInvocationException) + { + // Constructor threw an exception, fall back to Structure + } + catch (MethodAccessException) + { + // No permission to create instance, fall back to Structure + } + catch (NotSupportedException) + { + // Type is not supported for instantiation, fall back to Structure + } + } + return dataTypeId; } diff --git a/Tests/Opc.Ua.Core.Tests/Types/Constants/DataTypesTests.cs b/Tests/Opc.Ua.Core.Tests/Types/Constants/DataTypesTests.cs index 585ae737d..591e584ac 100644 --- a/Tests/Opc.Ua.Core.Tests/Types/Constants/DataTypesTests.cs +++ b/Tests/Opc.Ua.Core.Tests/Types/Constants/DataTypesTests.cs @@ -165,5 +165,62 @@ public void GetBrowseName_GetIdentifier_AreInverseOperations() Assert.AreEqual((uint)id, retrievedId); } } + + /// + /// Test GetDataTypeId for EUInformation type returns specific DataTypeId (i=887) not Structure (i=22). + /// + [Test] + public void GetDataTypeId_EUInformationType_ReturnsSpecificDataTypeId() + { + NodeId dataTypeId = DataTypes.GetDataTypeId(typeof(EUInformation)); + + Assert.IsNotNull(dataTypeId); + Assert.AreEqual(DataTypes.EUInformation, (uint)dataTypeId.Identifier); + Assert.AreEqual(0, dataTypeId.NamespaceIndex); + Assert.AreNotEqual(DataTypes.Structure, (uint)dataTypeId.Identifier, + "Should return specific EUInformation DataTypeId (i=887), not generic Structure (i=22)"); + } + + /// + /// Test GetDataTypeId for EUInformation instance returns specific DataTypeId. + /// + [Test] + public void GetDataTypeId_EUInformationInstance_ReturnsSpecificDataTypeId() + { + var euInfo = new EUInformation("unit", "http://test.org"); + NodeId dataTypeId = DataTypes.GetDataTypeId(euInfo); + + Assert.IsNotNull(dataTypeId); + Assert.AreEqual(DataTypes.EUInformation, (uint)dataTypeId.Identifier); + Assert.AreEqual(0, dataTypeId.NamespaceIndex); + } + + /// + /// Test GetDataTypeId for various well-known IEncodeable types returns their specific DataTypeIds. + /// + [Test] + public void GetDataTypeId_WellKnownEncodeableTypes_ReturnsSpecificDataTypeIds() + { + // Test various well-known types that implement IEncodeable + var testCases = new[] + { + (typeof(EUInformation), DataTypes.EUInformation), + (typeof(Range), DataTypes.Range), + (typeof(Argument), DataTypes.Argument), + (typeof(EnumValueType), DataTypes.EnumValueType), + (typeof(TimeZoneDataType), DataTypes.TimeZoneDataType) + }; + + foreach (var (type, expectedId) in testCases) + { + NodeId dataTypeId = DataTypes.GetDataTypeId(type); + + Assert.IsNotNull(dataTypeId, $"DataTypeId should not be null for {type.Name}"); + Assert.AreEqual(expectedId, (uint)dataTypeId.Identifier, + $"DataTypeId for {type.Name} should be i={expectedId}, not i={dataTypeId.Identifier}"); + Assert.AreEqual(0, dataTypeId.NamespaceIndex, + $"NamespaceIndex should be 0 for {type.Name}"); + } + } } }