Create Opc Ua Serveur c# with custom structure

39 views Asked by At

I want to create a c# OpcUa server to simulate the presence of a Siemens 1500 PLC Opc Ua server to test our application. I've managed to create the server with simple directories and variables, but I can't manage to create an ExtensionObject variable with a given structure. I'd like to do it directly in the code and not go through xml and OpcUa Compiler.

This is how I created my slot object:

public class Slot : IEncodeable
{
    public int ProductId { get; set; }
    public string ProductName { get; set; }
    public int[] Location { get; set; }
    public int[] Rotation { get; set; }
    public int[] Dimension { get; set; }

    public Slot()
    {
       Location = new int[3] { 0, 0, 0 };
       Rotation = new int[3] { 0, 0, 0 };
       Dimension = new int[3] { 0, 0, 0 };
     }


      public bool IsEqual(IEncodeable encodeable)
      {
         if (encodeable == null || !(encodeable is Slot))
         {
             return false;
          }

          Slot other = (Slot)encodeable;

          if (ProductId != other.ProductId)
              return false;
          if (ProductName != other.ProductName)
              return false;
          if (!CompareArrays(Location, other.Location))
              return false;
          if (!CompareArrays(Rotation, other.Rotation))
              return false;
          if (!CompareArrays(Dimension, other.Dimension))
              return false;
          return true;
       }

       public object Clone()
       {
         // Créez une copie superficielle avec MemberwiseClone()
         return this.MemberwiseClone();
        }

        private bool CompareArrays(T[] array1, T[] array2)
        {
           if (array1 == null && array2 == null)
              return true;
           if (array1 == null || array2 == null)
              return false;
           if (array1.Length != array2.Length)
              return false;

           for (int i = 0; i < array1.Length; i++)
           {
              if (!Equals(array1[i], array2[i]))
                       return false;
           }
           return true;
        }
        public void Encode(IEncoder encoder)
        {
              encoder.WriteInt32("ProductId", ProductId);
              encoder.WriteString("ProductName", ProductName);
              encoder.WriteInt32Array("Location", Location);
              encoder.WriteInt32Array("Rotation", Rotation);
              encoder.WriteInt32Array("Dimension", Dimension);
         }

         public void Decode(IDecoder decoder)
         {
               ProductId = decoder.ReadInt32("ProductId");
               ProductName = decoder.ReadString("ProductName");
               Location =decoder.ReadInt32Array("Location").ToArray();
               Rotation = decoder.ReadInt32Array("Rotation").ToArray();
               Dimension = decoder.ReadInt32Array("Dimension").ToArray();
          }

          public ExpandedNodeId TypeId => new ExpandedNodeId("ns=2;n=SlotDataType", 2);
          public ExpandedNodeId BinaryEncodingId => new ExpandedNodeId("ns=2;n=SlotDataType.BinaryEncoding", 2);
          public ExpandedNodeId XmlEncodingId => new ExpandedNodeId("ns=2;n=SlotDataType.XmlEncoding", 2);

}

Write the type in CustomNodeManager2 this way:

protected void CreateSlotDataTypeState()
{
    var dataTypeId = new NodeId("SlotDataType", NamespaceIndex);
    var binaryEncodingId = new NodeId("SlotDataType.BinaryEncoding", 
     NamespaceIndex);

    // Créer les champs pour le type de données Slot
    List fields = new List
    {
         new StructureField
         {
            Name = "ProductId",
            DataType = DataTypeIds.Int32,
            ValueRank = ValueRanks.Scalar,
            IsOptional = false
          },
         new StructureField
         {
            Name = "ProductName",
            DataType = DataTypeIds.String,
            ValueRank = ValueRanks.Scalar,
            IsOptional = false
          },
         new StructureField
         {
             Name = "Location",
             DataType = DataTypeIds.Int32,
             ValueRank = ValueRanks.OneDimension,
             ArrayDimensions = new uint[] { 3 },
             IsOptional = false
         },
         new StructureField
         {
             Name = "Rotation",
             DataType = DataTypeIds.Int32,
             ValueRank = ValueRanks.OneDimension,
             ArrayDimensions = new uint[] { 3 },
             IsOptional = false
          },
          new StructureField
          {
              Name = "Dimension",
              DataType = DataTypeIds.Int32,
              ValueRank = ValueRanks.OneDimension,
              ArrayDimensions = new uint[] { 3 },
              IsOptional = false
           }
      };

     // Créer la définition de la structure pour Slot
     StructureDefinition structureDefinition = new StructureDefinition
     {
          BaseDataType = DataTypeIds.Structure,
          StructureType = StructureType.Structure,
          Fields = fields.ToArray()
      };

      // Enregistrer le type de données Slot
     var slotDataType = new DataTypeState
     {
          NodeId = dataTypeId,
          BrowseName = new QualifiedName("SlotDataType", NamespaceIndex),
          DisplayName = "Slot",
          Description = "A custom Slot data type.",
          IsAbstract = false,
          WriteMask = AttributeWriteMask.None,
          UserWriteMask = AttributeWriteMask.None,
          DataTypeDefinition = new ExtensionObject(structureDefinition)

     };

     var binaryEncoding = new BaseObjectState(slotDataType)
     {
          NodeId = binaryEncodingId,
          BrowseName = new QualifiedName("BinaryEncoding", NamespaceIndex),
          DisplayName = new LocalizedText("Binary Encoding"),
      };

      // Lier l'encodage binaire au type de données
      slotDataType.AddReference(ReferenceTypes.HasEncoding, false, binaryEncoding.NodeId);
      binaryEncoding.AddReference(ReferenceTypes.HasEncoding, true, slotDataType.NodeId);

     // Enregistrement du type Slot.
      EncodeableFactory.GlobalFactory.AddEncodeableType(typeof(Slot));
      AddPredefinedNode(SystemContext, binaryEncoding);
     // Ajoutez le DataTypeNode à l'espace d'adressage du serveur
      AddPredefinedNode(SystemContext, slotDataType);
}

and add my variable:

// Production Unit
FolderState pus = CreateFolder(null, "Pus");
pus.AddReference(ReferenceTypes.Organizes, true, ObjectIds.ObjectsFolder);
references.Add(new NodeStateReference(ReferenceTypes.Organizes, false, pus.NodeId));
pus.EventNotifier = EventNotifiers.SubscribeToEvents;
AddRootNotifier(pus);

// Create a variable node using the Slot data type
for (int i = 0; i < 2; i++)
{
     FolderState currentPu = CreateFolder(pus, $"Pu_{i}");
     currentPu.AddReference(ReferenceTypes.Organizes, true, 
     ObjectIds.ObjectsFolder);
     currentPu.EventNotifier = EventNotifiers.SubscribeToEvents;

     NodeId slotDataTypeId = new NodeId("SlotDataType", NamespaceIndex);

     Slot slot = new Slot
     {
         ProductId = 1,
         ProductName = "Product A",
         Location = new int[3] { 0, 0, 0 },
         Rotation = new int[3] { 0, 0, 0 },
         Dimension = new int[3] { 0, 0, 0 }
      };

      ExtensionObject extensionObject = new 
      ExtensionObject(slot.BinaryEncodingId,slot);

      // Créez ici la variable en utilisant le type de données 'Slot'.
      var slotVariable = new BaseDataVariableState(currentPu)
      {
          NodeId = new NodeId($"SlotVariable_{i}", NamespaceIndex),
          BrowseName = new QualifiedName($"SlotVariable_{i}", NamespaceIndex),
           DisplayName = $"Slot Variable {i}",
           TypeDefinitionId = VariableTypeIds.BaseDataVariableType,
           DataType = slotDataTypeId, // Utilisez l'NodeId du type de données 'Slot'.
           ValueRank = ValueRanks.Scalar,
           AccessLevel = AccessLevels.CurrentReadOrWrite,
           UserAccessLevel = AccessLevels.CurrentReadOrWrite,
           Historizing = false,
           Value = extensionObject

       };
       // Ajoutez la variable au modèle d'adresse
       currentPu.AddChild(slotVariable);
}
AddPredefinedNode(SystemContext, pus);

The object is created, but the OpcUa client can't decode the Byte[] object.

Thank you in advance for your help.

Best regards.

The object is created, but the OpcUa client can't decode the Byte[] object. Thank you in advance for your help. Best regards.

0

There are 0 answers