From a681a2a6d1870f34217fa4c2d0223bd76a0c84bc Mon Sep 17 00:00:00 2001 From: Archie Date: Mon, 8 Sep 2025 10:29:02 -0600 Subject: [PATCH 1/4] Minimize Enumerations complete --- NodeSetToAML.cs | 116 +++++++++++++++++----------- SystemTest/NodeSetFiles/TestAml.xml | 81 ++++++++++++++++++- SystemTest/TestEnums.cs | 22 +++++- 3 files changed, 170 insertions(+), 49 deletions(-) diff --git a/NodeSetToAML.cs b/NodeSetToAML.cs index 41d48f1..a3b8068 100644 --- a/NodeSetToAML.cs +++ b/NodeSetToAML.cs @@ -3211,32 +3211,16 @@ private void AddStructureFieldDefinition( AttributeFamilyType attribute, UANode RemoveUnwantedAttribute( structureFieldAttribute, "MaxStringLength" ); } - if (field.Description != null && field.Description.Length > 0) - { - List localizedTextList = new List(field.Description.Length); - foreach(NodeSet.LocalizedText description in field.Description) - { - localizedTextList.Add( - new Variant( - new LocalizedText(description.Locale, description.Value))); - } - Variant localizedTextArray = new Variant(localizedTextList); + AttributeType descriptionCreated = AddLocalizedTextArrayAttribute( + structureFieldAttribute, + "Description", + field.Description); - LocalizedText localizedText = new LocalizedText( - field.Description[0].Locale, field.Description[0].Value); - AddModifyAttribute(structureFieldAttribute.Attribute, - "Description", "LocalizedText", localizedTextArray, - bListOf: true); - RemoveUnwantedAttribute(structureFieldAttribute.Attribute["Description"], - "StructureFieldDefinition"); - } - else if (structureFieldAttribute.Attribute["Description"] != null) + if (descriptionCreated != null) { - RemoveUnwantedAttribute(structureFieldAttribute, "Description"); + RemoveUnwantedAttribute(descriptionCreated,"StructureFieldDefinition"); } - - // Remove the NodeId from the structure Field AttributeType nodeIdAttribute = structureFieldAttribute.Attribute[ "DataType" ]; if( nodeIdAttribute != null ) @@ -3297,8 +3281,27 @@ private void AddEnumerationFieldDefinition(AttributeFamilyType attribute, UANode enumValues.DecodedValue, bListOf: true); if (added != null) { + // Remove empty values RemoveNodeIdsFromDefinition(added); + foreach( AttributeType arrayElement in added.Attribute ) + { + List toBeRemoved = new List(); + + foreach ( AttributeType parameter in arrayElement.Attribute ) + { + if ( string.IsNullOrEmpty( parameter.Value ) ) + { + toBeRemoved.Add(parameter.Name); + } + } + + foreach (string removeAttribute in toBeRemoved) + { + RemoveUnwantedAttribute(arrayElement, removeAttribute); + } + } + AttributeType nodeIdAttribute = AddModifyAttribute(added.Attribute, "NodeId", "NodeId", new Variant(EnumValuesPropertyId)); @@ -3327,30 +3330,14 @@ private void AddEnumerationFieldDefinition(AttributeFamilyType attribute, UANode fieldAttribute.RecreateAttributeInstance(enumFieldSource); fieldAttribute.Name = fieldDefinition.Name; - AddModifyAttribute(fieldAttribute.Attribute, - "Name", "String", new Variant(fieldDefinition.Name)); - - LocalizedText descriptionLocalizedText = new LocalizedText(""); - - if (fieldDefinition.Description != null && fieldDefinition.Description.Length > 0) - { - descriptionLocalizedText = new LocalizedText( - fieldDefinition.Description[0].Locale, fieldDefinition.Description[0].Value); - } - - AddModifyAttribute(fieldAttribute.Attribute, "Description", "LocalizedText", - new Variant(descriptionLocalizedText)); + // Remove the Name + RemoveUnwantedAttribute(fieldAttribute, "Name"); - LocalizedText displayNameLocalizedText = new LocalizedText(""); + // Description is an array of LocalizedText - if (fieldDefinition.DisplayName != null && fieldDefinition.DisplayName.Length > 0) - { - displayNameLocalizedText = new LocalizedText( - fieldDefinition.DisplayName[0].Locale, fieldDefinition.DisplayName[0].Value); - } + AddLocalizedTextArrayAttribute(fieldAttribute, "Description", fieldDefinition.Description); - AddModifyAttribute(fieldAttribute.Attribute, "DisplayName", "LocalizedText", - new Variant(displayNameLocalizedText)); + AddLocalizedTextArrayAttribute( fieldAttribute, "DisplayName", fieldDefinition.DisplayName); AddModifyAttribute(fieldAttribute.Attribute, "Value", "Int32", new Variant(fieldDefinition.Value)); @@ -3400,6 +3387,49 @@ private AttributeFamilyType ProcessDataType(NodeSet.UANode node) return added; } + + private AttributeType AddLocalizedTextArrayAttribute( AttributeType attribute, + string name, NodeSet.LocalizedText[] texts) + { + AttributeType created = null; + + // Currently for Field Definitions + Variant localizedTextArray = LocalizedTextArrayAsVariant(texts); + if (localizedTextArray.TypeInfo != null && + localizedTextArray.TypeInfo.BuiltInType != null && + localizedTextArray.TypeInfo.BuiltInType == BuiltInType.Variant) + { + created = AddModifyAttribute(attribute.Attribute, name, "LocalizedText", + localizedTextArray, bListOf: true); + } + else + { + RemoveUnwantedAttribute(attribute, name); + } + + return created; + } + + private Variant LocalizedTextArrayAsVariant(NodeSet.LocalizedText[] array ) + { + Variant localizedTextArray = new Variant(); + + if (array != null && array.Length >= 0) + { + List localizedTextList = new List(array.Length); + foreach (NodeSet.LocalizedText text in array) + { + localizedTextList.Add( + new Variant( + new LocalizedText(text.Locale, text.Value))); + } + + localizedTextArray = new Variant(localizedTextList); + } + + return localizedTextArray; + } + #endregion #region INSTANCE diff --git a/SystemTest/NodeSetFiles/TestAml.xml b/SystemTest/NodeSetFiles/TestAml.xml index 4d1e558..8877a8e 100644 --- a/SystemTest/NodeSetFiles/TestAml.xml +++ b/SystemTest/NodeSetFiles/TestAml.xml @@ -367,6 +367,83 @@ + + Duplex + BNM Ethernet Base Info + https://reference.opcfoundation.org/v105/Core/docs/Part22/5.3.1/#5.3.1.1 + + ns=1;i=6238 + i=29 + + + + Full duplex. + + + Half duplex. + + + + + + + EnumValues + + i=68 + i=24210 + + + + + + i=7616 + + + + 0 + + de + Voll + + + de + Vollduplex + + + + + + + i=7616 + + + + 1 + + Half + + + Half duplex. + + + + + + + i=7616 + + + + 2 + + + + + + + + + http://opcfoundation.org/UA/FX/AML/TESTING @@ -3586,8 +3663,8 @@ diff --git a/SystemTest/TestEnums.cs b/SystemTest/TestEnums.cs index f0d0463..d82243e 100644 --- a/SystemTest/TestEnums.cs +++ b/SystemTest/TestEnums.cs @@ -178,10 +178,6 @@ public void TestFieldDefinition(uint nodeId, object enumObject) foreach (AttributeType fieldDefinition in enumFieldDefinition.Attribute) { - AttributeType nameAttribute = fieldDefinition.Attribute["Name"]; - Assert.IsNotNull(nameAttribute, "Field Definition Name is null"); - Assert.IsNotNull(nameAttribute.Value, "Field Definition Name Value is null"); - Assert.AreEqual(fieldDefinition.Name, nameAttribute.Value, "Field Definition Name does not match Attribute Name"); AttributeType valueAttribute = fieldDefinition.Attribute["Value"]; Assert.IsNotNull(valueAttribute, "Field Definition Value is null"); Assert.IsNotNull(valueAttribute.Value, "Field Definition Value-Value is null"); @@ -457,6 +453,24 @@ public void TestUnwantedAttribute(AttributeType attribute, string unwantedAttrib } } + [TestMethod, Timeout(TestHelper.UnitTestTimeout)] + public void TestForRemovedAttributes() + { + AttributeFamilyType objectToTest = GetTestAttribute("3008", foundation: false); + + AttributeType enumFieldDefinition = GetAttribute(objectToTest, "EnumFieldDefinition"); + AttributeType unknownElement = GetAttribute(enumFieldDefinition, "Unknown"); + Assert.IsNull(unknownElement.Attribute["DisplayName"]); + Assert.IsNull(unknownElement.Attribute["Description"]); + + AttributeType enumValues = GetAttribute(objectToTest, "EnumValues"); + AttributeType lastArrayElement = GetAttribute(enumValues, "2"); + Assert.IsNull(lastArrayElement.Attribute["DisplayName"]); + Assert.IsNull(lastArrayElement.Attribute["Description"]); + } + + + #endregion #region Helpers From 605a7b1193998f40b7fc7e80ff83b3058c7b0b47 Mon Sep 17 00:00:00 2001 From: Archie Date: Mon, 8 Sep 2025 11:57:57 -0600 Subject: [PATCH 2/4] Save Work In Progress --- NodeSetToAML.cs | 24 +++++++++++++++++ SystemTest/NodeSetFiles/TestAml.xml | 41 +++++++++++++++++++++++++++-- 2 files changed, 63 insertions(+), 2 deletions(-) diff --git a/NodeSetToAML.cs b/NodeSetToAML.cs index 41d48f1..24ef971 100644 --- a/NodeSetToAML.cs +++ b/NodeSetToAML.cs @@ -3113,6 +3113,8 @@ private void AddAttributeData( AttributeFamilyType attribute, UANode uaNode ) AddEnumerationFieldDefinition( attribute, uaNode ); + AddOptionSetFieldDefintion( attribute, uaNode ); + AttributeType nodeIdAttribute = AddModifyAttribute( attribute.Attribute,"NodeId", "NodeId", new Variant( uaNode.DecodedNodeId ) ); @@ -3366,6 +3368,28 @@ private void AddEnumerationFieldDefinition(AttributeFamilyType attribute, UANode } } + private void AddOptionSetFieldDefintion( AttributeFamilyType attribute, UANode uaNode ) + { + UADataType optionSetNode = uaNode as UADataType; + if (optionSetNode != null && + optionSetNode.Definition != null && + optionSetNode.Definition.IsOptionSet == true) + { + if ( optionSetNode.Definition.Field != null && + optionSetNode.Definition.Field.Length > 0 ) + { + // Create an OptionSetFieldDefinition Attribute + // Create the attributes and add + + } + else + { + bool unexpected = true; + } + } + } + + private AttributeFamilyType ProcessDataType(NodeSet.UANode node) { var typeNode = node as MarkdownProcessor.NodeSet.UADataType; diff --git a/SystemTest/NodeSetFiles/TestAml.xml b/SystemTest/NodeSetFiles/TestAml.xml index 4d1e558..af74826 100644 --- a/SystemTest/NodeSetFiles/TestAml.xml +++ b/SystemTest/NodeSetFiles/TestAml.xml @@ -367,6 +367,43 @@ + + + AlarmMask + A & C Summary + https://reference.opcfoundation.org/v105/Core/docs/Part9/8.3 + + ns=1;i=6239 + i=5 + + + + + + + + + OptionSetValues + + i=68 + i=32251 + + + + + Active + + + Unacknowledged + + + Unconfirmed + + + + + + http://opcfoundation.org/UA/FX/AML/TESTING @@ -3586,8 +3623,8 @@ From 6c60d00e52d313786cb2bc18a9618eb3ffbe9c1c Mon Sep 17 00:00:00 2001 From: Archie Date: Mon, 8 Sep 2025 16:55:48 -0600 Subject: [PATCH 3/4] Add Field Definition for OptionSets --- NodeSetToAML.cs | 32 ++++++++++++++- SystemTest/TestOptionSetEmptyValues.cs | 56 +++++++++++++++++++++----- 2 files changed, 77 insertions(+), 11 deletions(-) diff --git a/NodeSetToAML.cs b/NodeSetToAML.cs index 24ef971..05e9e2e 100644 --- a/NodeSetToAML.cs +++ b/NodeSetToAML.cs @@ -3378,9 +3378,37 @@ private void AddOptionSetFieldDefintion( AttributeFamilyType attribute, UANode u if ( optionSetNode.Definition.Field != null && optionSetNode.Definition.Field.Length > 0 ) { - // Create an OptionSetFieldDefinition Attribute - // Create the attributes and add + string path = BuildLibraryReference(ATLPrefix, Opc.Ua.Namespaces.OpcUa, "ListOfOptionSet"); + AttributeFamilyType optionSetFieldDefinition = m_cAEXDocument.FindByPath(path) as AttributeFamilyType; + AttributeType optionSetFields = new AttributeType( + new System.Xml.Linq.XElement(defaultNS + "Attribute")); + + optionSetFields.RecreateAttributeInstance(optionSetFieldDefinition as AttributeFamilyType); + optionSetFields.Name = "OptionSetFieldDefinition"; + optionSetFields.AdditionalInformation.Append(OpcUaTypeOnly); + + string optionSetPath = BuildLibraryReference(ATLPrefix, Opc.Ua.Namespaces.OpcUa, "OptionSet"); + AttributeFamilyType optionSetSource = m_cAEXDocument.FindByPath(optionSetPath) as AttributeFamilyType; + + foreach (DataTypeField fieldDefinition in optionSetNode.Definition.Field) + { + AttributeType fieldAttribute = new AttributeType(new System.Xml.Linq.XElement(defaultNS + "Attribute")); + + fieldAttribute.RecreateAttributeInstance(optionSetSource); + fieldAttribute.Name = fieldDefinition.Name; + + AttributeType valueAttribute = AddModifyAttribute(fieldAttribute.Attribute, + "Value", "Int32", new Variant(fieldDefinition.Value)); + + RemoveUnwantedAttribute(valueAttribute, "NodeId"); + RemoveUnwantedAttribute(fieldAttribute, "ValidBits"); + RemoveUnwantedAttribute(fieldAttribute, "NodeId"); + + optionSetFields.Attribute.Insert(fieldAttribute, false, true); + } + + attribute.Attribute.Insert(optionSetFields, false, true); } else { diff --git a/SystemTest/TestOptionSetEmptyValues.cs b/SystemTest/TestOptionSetEmptyValues.cs index 50f65b3..20048ee 100644 --- a/SystemTest/TestOptionSetEmptyValues.cs +++ b/SystemTest/TestOptionSetEmptyValues.cs @@ -2,6 +2,8 @@ using Aml.Engine.CAEX.Extensions; using Aml.Engine.Services; using Microsoft.VisualStudio.TestTools.UnitTesting; +using Opc.Ua; +using System; using System.Collections.Generic; using System.Linq; @@ -10,8 +12,6 @@ namespace SystemTest [TestClass] public class TestOptionSetEmptyValues { - CAEXDocument m_document = null; - #region Tests [ TestMethod] @@ -86,19 +86,42 @@ public void TestInstance() TestOptionSet( value ); } + [TestMethod, Timeout(TestHelper.UnitTestTimeout)] + [DataRow("Active", "0")] + [DataRow("Unacknowledged", "1")] + [DataRow("Unconfirmed", "2")] + public void TestFieldDefinitions(string attributeName, string attributeValue) + { + CAEXDocument document = GetDocument("TestAml.xml.amlx"); + string amlId = TestHelper.BuildAmlId("", TestHelper.Uris.Test, "3009"); + CAEXObject initialObject = document.FindByID(amlId); + Assert.IsNotNull(initialObject, "Unable to find Initial Object"); + AttributeFamilyType theObject = initialObject as AttributeFamilyType; + Assert.IsNotNull(theObject, "Unable to Cast Initial Object"); + + + AttributeType fieldDefinition = GetAttribute( theObject.Attribute, "OptionSetFieldDefinition"); + AttributeType attribute = GetAttribute(fieldDefinition, attributeName); + + Assert.IsNull(attribute.Attribute["NodeId"]); + AttributeType valueAttribute = GetAttribute(attribute, "Value"); + Assert.AreEqual( valueAttribute.Value, attributeValue); + + Assert.IsNull(valueAttribute.Attribute[ "NodeId" ] ); + Assert.IsNull(valueAttribute.Attribute[ "ValidBits" ]); + } + + #endregion #region Helpers - private CAEXDocument GetDocument() + private CAEXDocument GetDocument(string fileName = "AmlFxTest.xml.amlx") { - if( m_document == null ) - { - m_document = TestHelper.GetReadOnlyDocument( "AmlFxTest.xml.amlx" ); - } - Assert.IsNotNull( m_document, "Unable to retrieve Document" ); - return m_document; + CAEXDocument document = TestHelper.GetReadOnlyDocument( fileName ); + Assert.IsNotNull(document, "Unable to retrieve Document" ); + return document; } public void TestOptionSet( AttributeTypeType attributeFamilyType ) @@ -138,6 +161,21 @@ public AttributeTypeLibType GetFxAcAttributes() return fxAcAttributes; } + public AttributeType GetAttribute(AttributeType attributeType, string attributeName) + { + Assert.IsNotNull(attributeType, "AttributeType is null"); + return GetAttribute(attributeType.Attribute, attributeName); + } + + public AttributeType GetAttribute(AttributeSequence attributes, string attributeName) + { + Assert.IsNotNull(attributes, "AttributeType is null"); + AttributeType result = attributes[attributeName]; + Assert.IsNotNull(result, "Unable to find Attribute " + attributeName); + return result; + } + + #endregion } From e6051fa64e1c66e63f6d3b9579fb0c922fd57c89 Mon Sep 17 00:00:00 2001 From: Archie Date: Wed, 10 Sep 2025 08:51:26 -0600 Subject: [PATCH 4/4] Add Test for TypeOnly --- SystemTest/NodeSetFiles/TestAml.xml | 2 +- SystemTest/TestOptionSetEmptyValues.cs | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/SystemTest/NodeSetFiles/TestAml.xml b/SystemTest/NodeSetFiles/TestAml.xml index af74826..b779173 100644 --- a/SystemTest/NodeSetFiles/TestAml.xml +++ b/SystemTest/NodeSetFiles/TestAml.xml @@ -368,7 +368,7 @@ - + AlarmMask A & C Summary https://reference.opcfoundation.org/v105/Core/docs/Part9/8.3 diff --git a/SystemTest/TestOptionSetEmptyValues.cs b/SystemTest/TestOptionSetEmptyValues.cs index 20048ee..03f5b64 100644 --- a/SystemTest/TestOptionSetEmptyValues.cs +++ b/SystemTest/TestOptionSetEmptyValues.cs @@ -101,6 +101,10 @@ public void TestFieldDefinitions(string attributeName, string attributeValue) AttributeType fieldDefinition = GetAttribute( theObject.Attribute, "OptionSetFieldDefinition"); + Assert.IsNotNull(fieldDefinition.AdditionalInformation); + Assert.AreEqual(1, fieldDefinition.AdditionalInformation.Count); + Assert.AreEqual("OpcUa:TypeOnly", fieldDefinition.AdditionalInformation[0]); + AttributeType attribute = GetAttribute(fieldDefinition, attributeName); Assert.IsNull(attribute.Attribute["NodeId"]);