Files
KinginfoGateway/framework/ThingsGateway.Plugin/ThingsGateway.OPCUA/OPCUAServer/ThingsGatewayNodeManager.cs

540 lines
19 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#region copyright
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
using Furion;
using Mapster;
using Newtonsoft.Json.Linq;
using Opc.Ua;
using Opc.Ua.Server;
using System;
using System.Collections.Generic;
using System.Linq;
using ThingsGateway.Application;
using ThingsGateway.Application.Extensions;
using ThingsGateway.Foundation;
using ThingsGateway.Foundation.Adapter.OPCUA;
using TouchSocket.Core;
namespace ThingsGateway.OPCUA;
/// <summary>
/// 数据节点
/// </summary>
public class ThingsGatewayNodeManager : CustomNodeManager2
{
private const string ReferenceServer = "https://diego2098.gitee.io/thingsgateway-docs/";
private readonly TypeAdapterConfig _config;
private readonly UploadDevice _device;
private readonly GlobalDeviceData _globalDeviceData;
private readonly RpcSingletonService _rpcCore;
private readonly ILog LogMessage;
/// <summary>
/// OPC和网关对应表
/// </summary>
private readonly Dictionary<NodeId, OPCUATag> NodeIdTags = new();
/// <inheritdoc cref="ThingsGatewayNodeManager"/>
public ThingsGatewayNodeManager(UploadDevice device, ILog log, IServerInternal server, ApplicationConfiguration configuration) : base(server, configuration, ReferenceServer)
{
_device = device;
LogMessage = log;
_rpcCore = App.GetService<RpcSingletonService>();
_globalDeviceData = App.GetService<GlobalDeviceData>();
_config = new TypeAdapterConfig();
_config.ForType<HistoryValue, DataValue>()
.Map(dest => dest.WrappedValue, (src) => new Variant(src.Value))
.Map(dest => dest.SourceTimestamp, src => DateTime.SpecifyKind(src.CollectTime, DateTimeKind.Utc))
.Map(dest => dest.StatusCode, (src) =>
src.IsOnline ? StatusCodes.Good : StatusCodes.Bad);
}
/// <summary>
/// 创建服务目录结构
/// </summary>
/// <param name="externalReferences"></param>
public override void CreateAddressSpace(IDictionary<NodeId, IList<IReference>> externalReferences)
{
lock (Lock)
{
if (!externalReferences.TryGetValue(ObjectIds.ObjectsFolder, out IList<IReference> references))
{
externalReferences[ObjectIds.ObjectsFolder] = references = new List<IReference>();
}
//首节点
FolderState rootFolder = CreateFolder(null, "ThingsGateway", "ThingsGateway");
rootFolder.AddReference(ReferenceTypes.Organizes, true, ObjectIds.ObjectsFolder);
references.Add(new NodeStateReference(ReferenceTypes.Organizes, false, rootFolder.NodeId));
rootFolder.EventNotifier = EventNotifiers.SubscribeToEvents;
AddRootNotifier(rootFolder);
//创建设备树
var _geviceGroup = _globalDeviceData.CollectDevices.ToList().Adapt<List<CollectDevice>>().GetTree();
// 开始寻找设备信息,并计算一些节点信息
foreach (var item in _geviceGroup)
{
//设备树会有两层
FolderState fs = CreateFolder(rootFolder, item.Name, item.Name);
fs.AddReference(ReferenceTypes.Organizes, true, ObjectIds.ObjectsFolder);
fs.EventNotifier = EventNotifiers.SubscribeToEvents;
if (item.Childrens?.Count > 0)
{
foreach (var item2 in item.Childrens)
{
AddTagNode(fs, item2.Name);
}
}
else
{
AddTagNode(fs, item.Name);
}
}
FolderState memoryfs = CreateFolder(null, "ThingsGateway中间变量", "ThingsGateway中间变量");
var variableRunTimes = _globalDeviceData.MemoryVariables;
foreach (var item in variableRunTimes)
{
CreateVariable(memoryfs, item);
}
AddPredefinedNode(SystemContext, rootFolder);
}
}
/// <summary>
/// 获取变量的属性值
/// </summary>
public string GetPropertyValue(DeviceVariableRunTime variableRunTime, string propertyName)
{
if (variableRunTime.VariablePropertys.ContainsKey(_device.Id))
{
var data = variableRunTime.VariablePropertys[_device.Id].FirstOrDefault(a =>
a.PropertyName == propertyName);
if (data != null)
{
return data.Value;
}
}
return null;
}
/// <summary>
/// 读取历史数据
/// </summary>
public override void HistoryRead(OperationContext context,
HistoryReadDetails details,
TimestampsToReturn timestampsToReturn,
bool releaseContinuationPoints,
IList<HistoryReadValueId> nodesToRead,
IList<HistoryReadResult> results,
IList<ServiceResult> errors)
{
base.HistoryRead(context, details, timestampsToReturn, releaseContinuationPoints, nodesToRead, results, errors);
//必须带有时间范围
if (details is not ReadRawModifiedDetails readDetail || readDetail.StartTime == DateTime.MinValue || readDetail.EndTime == DateTime.MinValue)
{
errors[0] = StatusCodes.BadHistoryOperationUnsupported;
return;
}
var service = ServiceHelper.GetBackgroundService<HistoryValueWorker>();
if (!service.StatuString.IsSuccess)
{
errors[0] = StatusCodes.BadHistoryOperationUnsupported;
return;
}
var db = service.GetHisDbAsync().GetAwaiter().GetResult();
if (!db.IsSuccess)
{
errors[0] = StatusCodes.BadHistoryOperationUnsupported;
return;
}
var startTime = readDetail.StartTime;
var endTime = readDetail.EndTime;
for (int i = 0; i < nodesToRead.Count; i++)
{
var historyRead = nodesToRead[i];
if (NodeIdTags.TryGetValue(historyRead.NodeId, out OPCUATag tag))
{
var data = db.Content.Queryable<HistoryValue>()
.Where(a => a.Name == tag.SymbolicName)
.Where(a => a.CollectTime >= startTime)
.Where(a => a.CollectTime <= endTime)
.ToList();
if (data.Count > 0)
{
var hisDataValue = data.Adapt<List<DataValue>>(_config);
HistoryData hisData = new();
hisData.DataValues.AddRange(hisDataValue);
errors[i] = StatusCodes.Good;
//切记Processed设为true否则客户端会报错
historyRead.Processed = true;
results[i] = new HistoryReadResult()
{
StatusCode = StatusCodes.Good,
HistoryData = new ExtensionObject(hisData)
};
}
else
{
results[i] = new HistoryReadResult()
{
StatusCode = StatusCodes.GoodNoData
};
}
}
else
{
results[i] = new HistoryReadResult()
{
StatusCode = StatusCodes.BadNotFound
};
}
}
}
/// <inheritdoc/>
public override NodeId New(ISystemContext context, NodeState node)
{
if (node is BaseInstanceState instance && instance.Parent != null)
{
string id = instance.Parent.NodeId.Identifier?.ToString();
if (id != null)
{
//用下划线分割
return new NodeId(id + "_" + instance.SymbolicName, instance.Parent.NodeId.NamespaceIndex);
}
}
return node.NodeId;
}
/// <summary>
/// 更新变量
/// </summary>
/// <param name="variable"></param>
public void UpVariable(VariableData variable)
{
var uaTag = NodeIdTags.Values.FirstOrDefault(it => it.SymbolicName == variable.Name);
if (uaTag == null) return;
object initialItemValue = null;
initialItemValue = variable.Value;
if (initialItemValue != null)
{
var code = variable.IsOnline ? StatusCodes.Good : StatusCodes.Bad;
if (code == StatusCodes.Good)
{
ChangeNodeData(uaTag, initialItemValue, variable.ChangeTime);
}
if (uaTag.StatusCode != code)
{
uaTag.StatusCode = code;
}
uaTag.ClearChangeMasks(SystemContext, false);
}
}
/// <summary>
/// <inheritdoc/>
/// </summary>
/// <param name="disposing"></param>
protected override void Dispose(bool disposing)
{
NodeIdTags.Clear();
base.Dispose(disposing);
}
private static NodeId DataNodeType(Type tp)
{
if (tp == typeof(bool))
return DataTypeIds.Boolean;
if (tp == typeof(byte))
return DataTypeIds.Byte;
if (tp == typeof(sbyte))
return DataTypeIds.SByte;
if (tp == typeof(short))
return DataTypeIds.Int16;
if (tp == typeof(ushort))
return DataTypeIds.UInt16;
if (tp == typeof(int))
return DataTypeIds.Int32;
if (tp == typeof(uint))
return DataTypeIds.UInt32;
if (tp == typeof(long))
return DataTypeIds.Int64;
if (tp == typeof(ulong))
return DataTypeIds.UInt64;
if (tp == typeof(float))
return DataTypeIds.Float;
if (tp == typeof(double))
return DataTypeIds.Double;
if (tp == typeof(string))
return DataTypeIds.String;
if (tp == typeof(DateTime))
return DataTypeIds.DateTime;
return DataTypeIds.String;
}
/// <summary>
/// 添加变量节点
/// </summary>
/// <param name="fs">设备组节点</param>
/// <param name="name">设备名称</param>
private void AddTagNode(FolderState fs, string name)
{
var device = _globalDeviceData.CollectDevices.Where(a => a.Name == name).FirstOrDefault();
if (device != null)
{
foreach (var item in device.DeviceVariableRunTimes)
{
CreateVariable(fs, item);
}
}
}
/// <summary>
/// 在服务器端直接更改对应数据节点的值
/// </summary>
private void ChangeNodeData(OPCUATag tag, object value, DateTime dateTime)
{
object newValue;
try
{
if (!tag.IsDataTypeInit)
{
if (tag.DataType == DataTypeIds.String)
{
if (value != null)
{
SetDataType(tag, value);
}
}
else
{
SetRank(tag, value);
}
}
var jToken = JToken.FromObject((tag.DataType == DataTypeIds.String ? value?.ToString() : value));
var dataValue = JsonUtils.DecoderObject(
this.Server.MessageContext,
tag.DataType,
TypeInfo.GetBuiltInType(tag.DataType, this.SystemContext.TypeTable),
jToken.CalculateActualValueRank(),
jToken
);
newValue = dataValue;
}
catch (Exception ex)
{
LogMessage.LogWarning(ex, "转化值错误");
newValue = value;
}
tag.Value = newValue;
tag.Timestamp = dateTime;
void SetDataType(OPCUATag tag, object value)
{
tag.IsDataTypeInit = true;
var tp = value.GetType();
if (tp == typeof(JArray))
{
try
{
tp = ((JValue)((JArray)value).FirstOrDefault()).Value.GetType();
tag.ValueRank = ValueRanks.OneOrMoreDimensions;
}
catch
{
}
}
if (tp == typeof(JValue))
{
tp = ((JValue)value).Value.GetType();
tag.ValueRank = ValueRanks.Scalar;
}
tag.DataType = DataNodeType(tp);
tag.ClearChangeMasks(SystemContext, false);
}
void SetRank(OPCUATag tag, object value)
{
tag.IsDataTypeInit = true;
var tp = value.GetType();
if (tp == typeof(JArray))
{
try
{
tp = ((JValue)((JArray)value).FirstOrDefault()).Value.GetType();
tag.ValueRank = ValueRanks.OneOrMoreDimensions;
}
catch
{
}
}
tag.ClearChangeMasks(SystemContext, false);
}
}
/// <summary>
/// 创建文件夹
/// </summary>
private FolderState CreateFolder(NodeState parent, string name, string description)
{
FolderState folder = new(parent)
{
SymbolicName = name,
ReferenceTypeId = ReferenceTypes.Organizes,
TypeDefinitionId = ObjectTypeIds.FolderType,
Description = description,
NodeId = new NodeId(name, NamespaceIndex),
BrowseName = new QualifiedName(name, NamespaceIndex),
DisplayName = new LocalizedText(name),
WriteMask = AttributeWriteMask.None,
UserWriteMask = AttributeWriteMask.None,
EventNotifier = EventNotifiers.None
};
parent?.AddChild(folder);
return folder;
}
/// <summary>
/// 创建一个值节点,类型需要在创建的时候指定
/// </summary>
private OPCUATag CreateVariable(NodeState parent, DeviceVariableRunTime variableRunTime)
{
OPCUATag variable = new(parent)
{
SymbolicName = variableRunTime.Name,
ReferenceTypeId = ReferenceTypes.Organizes,
TypeDefinitionId = VariableTypeIds.BaseDataVariableType,
NodeId = new NodeId(variableRunTime.Name, NamespaceIndex),
Description = variableRunTime.Description,
BrowseName = new QualifiedName(variableRunTime.Name, NamespaceIndex),
DisplayName = new LocalizedText(variableRunTime.Name),
WriteMask = AttributeWriteMask.DisplayName | AttributeWriteMask.Description,
UserWriteMask = AttributeWriteMask.DisplayName | AttributeWriteMask.Description,
ValueRank = ValueRanks.Scalar,
Id = variableRunTime.Id,
DataType = DataNodeType(variableRunTime)
};
var level = ProtectTypeTrans(variableRunTime);
variable.AccessLevel = level;
variable.UserAccessLevel = level;
variable.Historizing = variableRunTime.HisEnable;
variable.Value = Opc.Ua.TypeInfo.GetDefaultValue(variable.DataType, ValueRanks.Any, Server.TypeTree);
var code = variableRunTime.IsOnline ? StatusCodes.Good : StatusCodes.Bad;
variable.StatusCode = code;
variable.Timestamp = variableRunTime.CollectTime;
variable.OnWriteValue = OnWriteDataValue;
parent?.AddChild(variable);
NodeIdTags.AddOrUpdate(variable.NodeId, variable);
return variable;
}
/// <summary>
/// 网关转OPC数据类型
/// </summary>
/// <param name="variableRunTime"></param>
/// <returns></returns>
private NodeId DataNodeType(DeviceVariableRunTime variableRunTime)
{
var str = GetPropertyValue(variableRunTime, nameof(OPCUAServerVariableProperty.DataTypeEnum));
Type tp;
if (Enum.TryParse<DataTypeEnum>(str, out DataTypeEnum result))
{
tp = result.GetSystemType();
}
else
{
tp = variableRunTime.DataType;
}
return DataNodeType(tp);
}
private ServiceResult OnWriteDataValue(ISystemContext context, NodeState node, NumericRange indexRange, QualifiedName dataEncoding, ref object value, ref StatusCode statusCode, ref DateTime timestamp)
{
try
{
var context1 = context as ServerSystemContext;
if (context1.UserIdentity.TokenType == UserTokenType.Anonymous)
{
return StatusCodes.BadUserAccessDenied;
}
OPCUATag variable = node as OPCUATag;
if (NodeIdTags.TryGetValue(variable.NodeId, out OPCUATag tag))
{
if (StatusCode.IsGood(variable.StatusCode))
{
//仅当指定了值时才将值写入
if (variable.Value != null)
{
var result = _rpcCore.InvokeDeviceMethodAsync("OPCUASERVER-" + context1?.OperationContext?.Session?.Identity?.DisplayName,
new()
{
{ variable.SymbolicName, value?.ToString() }
}
).ConfigureAwait(true).GetAwaiter().GetResult();
if (result.Values.FirstOrDefault().IsSuccess)
{
return StatusCodes.Good;
}
else
{
return new(StatusCodes.BadWaitingForResponse, result.Values.FirstOrDefault().Message);
}
}
}
}
return StatusCodes.BadWaitingForResponse;
}
catch
{
return StatusCodes.BadTypeMismatch;
}
}
private byte ProtectTypeTrans(DeviceVariableRunTime variableRunTime)
{
byte result = 0;
result = variableRunTime.ProtectTypeEnum switch
{
ProtectTypeEnum.ReadOnly => (byte)(result | AccessLevels.CurrentRead),
ProtectTypeEnum.ReadWrite => (byte)(result | AccessLevels.CurrentReadOrWrite),
_ => (byte)(result | AccessLevels.CurrentRead),
};
if (variableRunTime.HisEnable)
{
result = (byte)(result | AccessLevels.HistoryRead);
}
return result;
}
}