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
相关产品推荐
相关产品推荐

