You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C# OPC UA服务器自定义结构:客户端无法解码Byte[]求助

问题描述

用C#创建OPC UA服务器模拟西门子1500 PLC,已实现基础目录和变量,但无法通过代码直接创建带指定结构的ExtensionObject变量(不想使用XML和OPC UA编译器)。对象已创建完成,但OPC UA客户端无法解码返回的Byte[]对象,需要技术支持。

Slot数据类型实现代码
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()
     {
       // 创建浅拷贝
       return this.MemberwiseClone();
      }

      private bool CompareArrays<T>(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("SlotDataType", NamespaceIndex);
        public ExpandedNodeId BinaryEncodingId => new ExpandedNodeId("SlotDataType.BinaryEncoding", NamespaceIndex);
        public ExpandedNodeId XmlEncodingId => new ExpandedNodeId("SlotDataType.XmlEncoding", NamespaceIndex);
}
自定义节点管理器中注册Slot数据类型的代码
protected void CreateSlotDataTypeState()
{
    var dataTypeId = new NodeId("SlotDataType", NamespaceIndex);
    var binaryEncodingId = new NodeId("SlotDataType.BinaryEncoding", NamespaceIndex);

    // 创建Slot数据类型的字段
    List<StructureField> fields = new List<StructureField>
    {
         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
           }
      };

     // 创建Slot的结构定义
     StructureDefinition structureDefinition = new StructureDefinition
     {
          BaseDataType = DataTypeIds.Structure,
          StructureType = StructureType.Structure,
          Fields = fields.ToArray()
      };

      // 注册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 DataTypeState(slotDataType)
     {
          NodeId = binaryEncodingId,
          BrowseName = new QualifiedName("BinaryEncoding", NamespaceIndex),
          DisplayName = new LocalizedText("Binary Encoding"),
          DataType = DataTypeIds.XmlElement,
          ValueRank = ValueRanks.Scalar
      };

      // 关联数据类型与二进制编码
      slotDataType.AddReference(ReferenceTypes.HasEncoding, false, binaryEncoding.NodeId);
      binaryEncoding.AddReference(ReferenceTypes.HasEncoding, true, slotDataType.NodeId);

     // 注册Slot可编码类型
      EncodeableFactory.GlobalFactory.AddEncodeableType(typeof(Slot));
      AddPredefinedNode(SystemContext, binaryEncoding);
      // 将DataTypeNode添加到服务器地址空间
      AddPredefinedNode(SystemContext, slotDataType);
}
添加Slot变量的代码
// 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);

// 确保先注册数据类型
CreateSlotDataTypeState();

// 创建使用Slot数据类型的变量节点
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 }
      };

      // 直接传入Slot实例,框架自动处理编码ID
      ExtensionObject extensionObject = new ExtensionObject(slot);

      // 创建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,
           ValueRank = ValueRanks.Scalar,
           AccessLevel = AccessLevels.CurrentReadOrWrite,
           UserAccessLevel = AccessLevels.CurrentReadOrWrite,
           Historizing = false,
           Value = extensionObject
       };
       // 将变量添加到地址模型
       currentPu.AddChild(slotVariable);
}
AddPredefinedNode(SystemContext, pus);
解决客户端无法解码Byte[]的关键修改点
  • 统一命名空间索引:Slot类中的TypeId和BinaryEncodingId去掉硬编码的ns=2,改用NamespaceIndex变量,确保和节点管理器中的命名空间索引一致,避免ID不匹配。
  • 修正BinaryEncoding节点类型:将原来的BaseObjectState改为DataTypeState,并指定DataType = DataTypeIds.XmlElement,符合OPC UA规范中编码节点的类型要求。
  • 正确构造ExtensionObject:创建ExtensionObject时直接传入Slot实例,不需要手动指定BinaryEncodingId,IEncodeable接口会自动提供正确的编码ID,避免编码配置错误。
  • 调整注册顺序:在创建变量之前必须先调用CreateSlotDataTypeState()注册数据类型,确保服务器在编码变量值时能找到对应的类型定义和编码规则。
  • 验证字段顺序一致性:确保StructureDefinition中的字段顺序和Slot类Encode/Decode方法中的读写顺序完全一致,OPC UA二进制编码对字段顺序敏感,不一致会导致解码失败。

内容的提问来源于stack exchange,提问作者Julien MARTIN

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.28 19:14:54