240 lines
9.2 KiB
C#
240 lines
9.2 KiB
C#
using Opc.Ua;
|
||
using Opc.Ua.Client;
|
||
using System.Reflection;
|
||
using System.Text;
|
||
using System.Text.Json;
|
||
using System.Text.Json.Serialization;
|
||
|
||
namespace Plugin.Driver.DeviceOpcUa;
|
||
|
||
public static class ExtensionObjectHelper
|
||
{
|
||
public static string ReadStruct(string address, DataValue value, ISession? session)
|
||
{
|
||
if (session == null) return string.Empty;
|
||
if (value == null || value.Value == null) return string.Empty;
|
||
NodeId node = new NodeId(address);
|
||
//获取Node 信息 验证Node的数据类型
|
||
VariableNode? nodeInfo = session.ReadNode(node) as VariableNode;
|
||
if (nodeInfo == null) return string.Empty;
|
||
var datatypeNode = (session.ReadNode(nodeInfo.DataType)) as DataTypeNode;
|
||
if (datatypeNode == null) return string.Empty;
|
||
var typeDefine = datatypeNode.DataTypeDefinition.Body as StructureDefinition;
|
||
if (typeDefine == null) return string.Empty;
|
||
int index = 0;
|
||
if (value.Value is ExtensionObject[])//数组
|
||
{
|
||
var res = new Dictionary<int, object?>();
|
||
var values = value.Value as ExtensionObject[];
|
||
if (values == null) return string.Empty;
|
||
int i = 0;
|
||
foreach (var item in values)
|
||
{
|
||
var obj = GetJsonFromExtensionObject(address, item, typeDefine, session, ref index);
|
||
if (obj != null)
|
||
{
|
||
res[i] = obj;
|
||
}
|
||
i++;
|
||
}
|
||
var jsonOptions = new JsonSerializerOptions
|
||
{
|
||
WriteIndented = true,
|
||
ReferenceHandler = ReferenceHandler.IgnoreCycles,
|
||
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
|
||
};
|
||
return JsonSerializer.Serialize(res, jsonOptions);
|
||
}
|
||
else //非数组
|
||
{
|
||
var values = value.Value as ExtensionObject;
|
||
if (values == null) return string.Empty;
|
||
var obj = GetJsonFromExtensionObject(address, values, typeDefine, session, ref index);
|
||
if (obj == null) return string.Empty;
|
||
var jsonOptions = new JsonSerializerOptions
|
||
{
|
||
WriteIndented = true,
|
||
ReferenceHandler = ReferenceHandler.IgnoreCycles,
|
||
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
|
||
};
|
||
return JsonSerializer.Serialize(obj, jsonOptions);
|
||
}
|
||
|
||
|
||
}
|
||
|
||
|
||
|
||
|
||
private static object? GetJsonFromExtensionObject(string address, ExtensionObject value, StructureDefinition structure, ISession session, ref int index)
|
||
{
|
||
var res = new Dictionary<string, object?>();
|
||
var data = value.Body as byte[];
|
||
if (data == null) return null;
|
||
foreach (var field in structure.Fields)
|
||
{
|
||
if (field.ValueRank == 1)
|
||
{
|
||
//是数组,需要读取数组长度
|
||
string fieldNodeId = $"{address}.{field.Name}"; // 例如:"StationStatus1.AlarmA1"
|
||
VariableNode? fieldNode = session.ReadNode(new NodeId(fieldNodeId)) as VariableNode;
|
||
if (fieldNode == null)
|
||
{
|
||
return null;
|
||
}
|
||
else if (fieldNode.ArrayDimensions != null && fieldNode.ArrayDimensions.Count > 0)
|
||
{
|
||
int count = (int)BitConverter.ToUInt32(data.Skip(index).Take(4).ToArray(), 0);
|
||
index += 4;
|
||
for (int i = 0; i < count; i++)
|
||
{
|
||
res[$"{field.Name}[{i}]"] = BitConverter.ToBoolean(data.Skip(index + i).Take(1).ToArray(), 0);
|
||
}
|
||
index += count;
|
||
}
|
||
}
|
||
else if (field.DataType.NamespaceIndex == 2)
|
||
{
|
||
//结构体嵌套
|
||
var count = BitConverter.ToUInt32(data.Skip(index).Take(4).ToArray(), 0);
|
||
index += 4;
|
||
var datatypeNode1 = (session.ReadNode(field.DataType)) as DataTypeNode;
|
||
if (datatypeNode1 == null) return null;
|
||
var typeDefine = datatypeNode1.DataTypeDefinition.Body as StructureDefinition;
|
||
if (typeDefine == null) return null;
|
||
for (int i = 0; i < count; i++)
|
||
{
|
||
res[field.Name + i] = GetJsonFromExtensionObject(address, value, typeDefine, session, ref index);
|
||
}
|
||
}
|
||
else
|
||
{
|
||
string name = field.Name;
|
||
if (field.DataType == DataTypeIds.String)
|
||
{
|
||
int length = (int)BitConverter.ToUInt32(data.Skip(index).Take(4).ToArray(), 0);
|
||
index += 4;
|
||
string re = Encoding.Default.GetString(data.Skip(index).Take(length).ToArray());
|
||
res[name] = re;
|
||
index += length;
|
||
}
|
||
else if (field.DataType == DataTypeIds.UInt32)
|
||
{
|
||
UInt32 re = BitConverter.ToUInt32(data.Skip(index).Take(4).ToArray(), 0);
|
||
index += 4;
|
||
res[name] = re;
|
||
}
|
||
else if (field.DataType == DataTypeIds.Float)
|
||
{
|
||
float re = BitConverter.ToSingle(data.Skip(index).Take(4).ToArray(), 0);
|
||
index += 4;
|
||
res[name] = re;
|
||
}
|
||
else if (field.DataType == DataTypeIds.Boolean)
|
||
{
|
||
bool re = BitConverter.ToBoolean(data.Skip(index).Take(1).ToArray());
|
||
res[name] = re;
|
||
index += 1;
|
||
}
|
||
else if (field.DataType == DataTypeIds.Double)
|
||
{
|
||
double re = BitConverter.ToDouble(data.Skip(index).Take(8).ToArray());
|
||
res[name] = re;
|
||
index += 8;
|
||
}
|
||
else if (field.DataType == DataTypeIds.Int16)
|
||
{
|
||
Int16 re = BitConverter.ToInt16(data.Skip(index).Take(2).ToArray());
|
||
res[name] = re;
|
||
index += 2;
|
||
}
|
||
else if (field.DataType == DataTypeIds.UInt16)
|
||
{
|
||
UInt16 re = BitConverter.ToUInt16(data.Skip(index).Take(2).ToArray());
|
||
res[name] = re;
|
||
index += 2;
|
||
}
|
||
}
|
||
|
||
|
||
}
|
||
return res;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 解析ExtensionObject为可序列化的字典对象
|
||
/// </summary>
|
||
/// <param name="extensionObject">OPC UA扩展对象</param>
|
||
/// <returns>解析后的键值对字典</returns>
|
||
public static object? ResolveExtensionObject(object extensionObject)
|
||
{
|
||
if (extensionObject == null)
|
||
return null;
|
||
|
||
try
|
||
{
|
||
// 适配不同OPC UA库的ExtensionObject类型(根据实际情况调整)
|
||
Type extObjType = extensionObject.GetType();
|
||
|
||
// 方式1:如果是已知的结构体类型,直接转换
|
||
if (extObjType.IsValueType && !extObjType.IsPrimitive && !extObjType.IsEnum)
|
||
{
|
||
return ConvertStructToDictionary(extensionObject);
|
||
}
|
||
|
||
// 方式2:适配OPC UA标准ExtensionObject(根据实际库调整属性名)
|
||
PropertyInfo? bodyProp = extObjType?.GetProperty("Body") ??
|
||
extObjType?.GetProperty("WrappedValue") ??
|
||
extObjType?.GetProperty("Value");
|
||
|
||
if (bodyProp != null)
|
||
{
|
||
object? bodyValue = bodyProp?.GetValue(extensionObject);
|
||
if (bodyValue != null && bodyValue.GetType().IsValueType)
|
||
{
|
||
return ConvertStructToDictionary(bodyValue);
|
||
}
|
||
return bodyValue;
|
||
}
|
||
|
||
// 方式3:无法解析时返回原始对象(最终会序列化为JSON)
|
||
return extensionObject;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
// 解析失败时记录异常并返回原始对象
|
||
System.Diagnostics.Debug.WriteLine($"解析结构体失败: {ex.Message}");
|
||
return extensionObject;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 将结构体转换为字典(便于JSON序列化)
|
||
/// </summary>
|
||
/// <param name="structObj">结构体对象</param>
|
||
/// <returns>键值对字典</returns>
|
||
private static object? ConvertStructToDictionary(object structObj)
|
||
{
|
||
if (structObj == null)
|
||
return null;
|
||
|
||
Type structType = structObj.GetType();
|
||
var dict = new System.Collections.Generic.Dictionary<string, object?>();
|
||
|
||
// 获取结构体的所有公共字段和属性
|
||
foreach (FieldInfo field in structType.GetFields(BindingFlags.Public | BindingFlags.Instance))
|
||
{
|
||
dict[field.Name] = field?.GetValue(structObj);
|
||
}
|
||
|
||
foreach (PropertyInfo prop in structType.GetProperties(BindingFlags.Public | BindingFlags.Instance))
|
||
{
|
||
if (prop.CanRead)
|
||
{
|
||
dict[prop.Name] = prop.GetValue(structObj);
|
||
}
|
||
}
|
||
return dict;
|
||
}
|
||
}
|