#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; } }