#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;
///
/// 数据节点
///
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;
///
/// OPC和网关对应表
///
private readonly Dictionary NodeIdTags = new();
///
public ThingsGatewayNodeManager(UploadDevice device, ILog log, IServerInternal server, ApplicationConfiguration configuration) : base(server, configuration, ReferenceServer)
{
_device = device;
LogMessage = log;
_rpcCore = App.GetService();
_globalDeviceData = App.GetService();
_config = new TypeAdapterConfig();
_config.ForType()
.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);
}
///
/// 创建服务目录结构
///
///
public override void CreateAddressSpace(IDictionary> externalReferences)
{
lock (Lock)
{
if (!externalReferences.TryGetValue(ObjectIds.ObjectsFolder, out IList references))
{
externalReferences[ObjectIds.ObjectsFolder] = references = new List();
}
//首节点
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>().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);
}
}
///
/// 获取变量的属性值
///
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;
}
///
/// 读取历史数据
///
public override void HistoryRead(OperationContext context,
HistoryReadDetails details,
TimestampsToReturn timestampsToReturn,
bool releaseContinuationPoints,
IList nodesToRead,
IList results,
IList 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();
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()
.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>(_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
};
}
}
}
///
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;
}
///
/// 更新变量
///
///
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);
}
}
///
///
///
///
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;
}
///
/// 添加变量节点
///
/// 设备组节点
/// 设备名称
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);
}
}
}
///
/// 在服务器端直接更改对应数据节点的值
///
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);
}
}
///
/// 创建文件夹
///
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;
}
///
/// 创建一个值节点,类型需要在创建的时候指定
///
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;
}
///
/// 网关转OPC数据类型
///
///
///
private NodeId DataNodeType(DeviceVariableRunTime variableRunTime)
{
var str = GetPropertyValue(variableRunTime, nameof(OPCUAServerVariableProperty.DataTypeEnum));
Type tp;
if (Enum.TryParse(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;
}
}