mirror of
				https://gitee.com/ThingsGateway/ThingsGateway.git
				synced 2025-10-31 07:33:58 +08:00 
			
		
		
		
	
		
			
				
	
	
		
			409 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
			
		
		
	
	
			409 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
| //------------------------------------------------------------------------------
 | ||
| //  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
 | ||
| //  此代码版权(除特别声明外的代码)归作者本人Diego所有
 | ||
| //  源代码使用协议遵循本仓库的开源协议及附加协议
 | ||
| //  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
 | ||
| //  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
 | ||
| //  使用文档:https://thingsgateway.cn/
 | ||
| //  QQ群:605534569
 | ||
| //------------------------------------------------------------------------------
 | ||
| 
 | ||
| using Newtonsoft.Json.Linq;
 | ||
| 
 | ||
| using Opc.Ua;
 | ||
| using Opc.Ua.Client;
 | ||
| 
 | ||
| using ThingsGateway.Foundation.Extension.Generic;
 | ||
| using ThingsGateway.Foundation.OpcUa;
 | ||
| using ThingsGateway.Gateway.Application;
 | ||
| using ThingsGateway.NewLife.Json.Extension;
 | ||
| using ThingsGateway.NewLife.Threading;
 | ||
| 
 | ||
| using TouchSocket.Core;
 | ||
| 
 | ||
| namespace ThingsGateway.Plugin.OpcUa;
 | ||
| 
 | ||
| /// <summary>
 | ||
| /// <inheritdoc/>
 | ||
| /// </summary>
 | ||
| public class OpcUaMaster : CollectBase
 | ||
| {
 | ||
|     private readonly OpcUaMasterProperty _driverProperties = new();
 | ||
| 
 | ||
|     private ThingsGateway.Foundation.OpcUa.OpcUaMaster _plc;
 | ||
| 
 | ||
|     private CancellationToken _token;
 | ||
| 
 | ||
|     private volatile bool connectFirstFailLoged;
 | ||
|     private volatile bool success = true;
 | ||
| 
 | ||
|     /// <inheritdoc/>
 | ||
|     public override CollectPropertyBase CollectProperties => _driverProperties;
 | ||
| 
 | ||
|     /// <inheritdoc/>
 | ||
|     public override Type DriverDebugUIType => typeof(ThingsGateway.Debug.OpcUaMaster);
 | ||
| 
 | ||
|     public override Type DriverPropertyUIType => typeof(OpcUaMasterPropertyRazor);
 | ||
| 
 | ||
| 
 | ||
| 
 | ||
|     protected override async Task InitChannelAsync(IChannel? channel = null)
 | ||
|     {
 | ||
| 
 | ||
| 
 | ||
|         //载入配置
 | ||
|         OpcUaProperty config = new()
 | ||
|         {
 | ||
|             OpcUrl = _driverProperties.OpcUrl,
 | ||
|             UpdateRate = _driverProperties.UpdateRate,
 | ||
|             DeadBand = _driverProperties.DeadBand,
 | ||
|             GroupSize = _driverProperties.GroupSize,
 | ||
|             KeepAliveInterval = _driverProperties.KeepAliveInterval,
 | ||
|             UseSecurity = _driverProperties.UseSecurity,
 | ||
|             ActiveSubscribe = _driverProperties.ActiveSubscribe,
 | ||
|             UserName = _driverProperties.UserName,
 | ||
|             Password = _driverProperties.Password,
 | ||
|             CheckDomain = _driverProperties.CheckDomain,
 | ||
|             LoadType = _driverProperties.LoadType,
 | ||
|             AutoAcceptUntrustedCertificates = _driverProperties.AutoAcceptUntrustedCertificates,
 | ||
|         };
 | ||
|         if (_plc == null)
 | ||
|         {
 | ||
|             _plc = new();
 | ||
|             _plc.LogEvent += _plc_LogEvent;
 | ||
|             _plc.DataChangedHandler += DataChangedHandler;
 | ||
|         }
 | ||
|         _plc.OpcUaProperty = config;
 | ||
|         await base.InitChannelAsync(channel).ConfigureAwait(false);
 | ||
|     }
 | ||
| 
 | ||
|     /// <inheritdoc/>
 | ||
|     public override bool IsConnected() => _plc?.Connected == true;
 | ||
| 
 | ||
|     public override string ToString()
 | ||
|     {
 | ||
|         return $"{_driverProperties.OpcUrl}";
 | ||
|     }
 | ||
| 
 | ||
|     /// <inheritdoc/>
 | ||
|     protected override void Dispose(bool disposing)
 | ||
|     {
 | ||
|         if (_plc != null)
 | ||
|         {
 | ||
|             _plc.DataChangedHandler -= DataChangedHandler;
 | ||
|             _plc.LogEvent -= _plc_LogEvent;
 | ||
| 
 | ||
|             _plc.Disconnect();
 | ||
|             _plc.SafeDispose();
 | ||
|         }
 | ||
|         base.Dispose(disposing);
 | ||
|     }
 | ||
| 
 | ||
|     public override string GetAddressDescription()
 | ||
|     {
 | ||
|         return _plc?.GetAddressDescription();
 | ||
|     }
 | ||
| 
 | ||
|     protected override async Task ProtectedStartAsync(CancellationToken cancellationToken)
 | ||
|     {
 | ||
|         _token = cancellationToken;
 | ||
|         try
 | ||
|         {
 | ||
|             await _plc.ConnectAsync(cancellationToken).ConfigureAwait(false);
 | ||
|         }
 | ||
|         catch (Exception ex)
 | ||
|         {
 | ||
|             LogMessage?.LogWarning(ex, "Connect Fail");
 | ||
|         }
 | ||
|         await base.ProtectedStartAsync(cancellationToken).ConfigureAwait(false);
 | ||
|     }
 | ||
| 
 | ||
|     protected override async ValueTask ProtectedExecuteAsync(CancellationToken cancellationToken)
 | ||
|     {
 | ||
|         if (_plc.Session == null)
 | ||
|         {
 | ||
|             try
 | ||
|             {
 | ||
|                 await Task.Delay(100, cancellationToken).ConfigureAwait(false);
 | ||
|                 if (_plc.Session == null)
 | ||
|                     await _plc.ConnectAsync(cancellationToken).ConfigureAwait(false);
 | ||
|             }
 | ||
|             catch (Exception ex)
 | ||
|             {
 | ||
|                 if (!connectFirstFailLoged)
 | ||
|                     LogMessage?.LogWarning(ex, "Connect Fail");
 | ||
| 
 | ||
|                 connectFirstFailLoged = true;
 | ||
|                 CurrentDevice.SetDeviceStatus(TimerX.Now, true, ex.Message);
 | ||
|                 await Task.Delay(10000, cancellationToken).ConfigureAwait(false);
 | ||
|             }
 | ||
|         }
 | ||
|         if (_driverProperties.ActiveSubscribe)
 | ||
|         {
 | ||
| 
 | ||
|             //获取设备连接状态
 | ||
|             if (IsConnected())
 | ||
|             {
 | ||
| 
 | ||
| 
 | ||
|                 //更新设备活动时间
 | ||
|                 CurrentDevice.SetDeviceStatus(TimerX.Now, false);
 | ||
| 
 | ||
|                 //如果是订阅模式,连接时添加订阅组
 | ||
|                 if (_plc.OpcUaProperty?.ActiveSubscribe == true && CurrentDevice.VariableSourceReads.Count > 0 && _plc.Session.SubscriptionCount < CurrentDevice.VariableSourceReads.Count)
 | ||
|                 {
 | ||
|                     try
 | ||
|                     {
 | ||
| 
 | ||
|                         foreach (var variableSourceRead in CurrentDevice.VariableSourceReads)
 | ||
|                         {
 | ||
|                             if (_plc.Session.Subscriptions.FirstOrDefault(a => a.DisplayName == variableSourceRead.RegisterAddress) == null)
 | ||
|                             {
 | ||
|                                 await _plc.AddSubscriptionAsync(variableSourceRead.RegisterAddress, variableSourceRead.VariableRuntimes.Where(a => !a.RegisterAddress.IsNullOrEmpty()).Select(a => a.RegisterAddress!).ToHashSet().ToArray(), _plc.OpcUaProperty.LoadType, cancellationToken).ConfigureAwait(false);
 | ||
| 
 | ||
|                                 LogMessage?.LogInformation($"AddSubscription index  {CurrentDevice.VariableSourceReads.IndexOf(variableSourceRead)}  done");
 | ||
| 
 | ||
|                             }
 | ||
|                         }
 | ||
|                         LogMessage?.LogInformation("AddSubscriptions done");
 | ||
|                     }
 | ||
|                     catch (Exception ex)
 | ||
|                     {
 | ||
|                         LogMessage?.LogWarning(ex, "AddSubscriptions");
 | ||
|                     }
 | ||
|                     finally
 | ||
|                     {
 | ||
|                         await Task.Delay(30000, cancellationToken).ConfigureAwait(false);
 | ||
|                     }
 | ||
|                 }
 | ||
| 
 | ||
|             }
 | ||
|             else
 | ||
|             {
 | ||
|                 CurrentDevice.SetDeviceStatus(TimerX.Now, true);
 | ||
|             }
 | ||
| 
 | ||
|             ScriptVariableRun(cancellationToken);
 | ||
|         }
 | ||
|         else
 | ||
|         {
 | ||
|             await base.ProtectedExecuteAsync(cancellationToken).ConfigureAwait(false);
 | ||
|         }
 | ||
|     }
 | ||
| 
 | ||
|     /// <inheritdoc/>
 | ||
|     protected override async Task<List<VariableSourceRead>> ProtectedLoadSourceReadAsync(List<VariableRuntime> deviceVariables)
 | ||
|     {
 | ||
|         await Task.CompletedTask.ConfigureAwait(false);
 | ||
|         if (deviceVariables.Count > 0)
 | ||
|         {
 | ||
|             var dataLists = deviceVariables.ChunkBetter(_driverProperties.GroupSize);
 | ||
| 
 | ||
|             var dataResult = new List<VariableSourceRead>();
 | ||
|             foreach (var variable in dataLists)
 | ||
|             {
 | ||
|                 var sourVars = new VariableSourceRead()
 | ||
|                 {
 | ||
|                     TimeTick = new(_driverProperties.UpdateRate.ToString()),
 | ||
|                     RegisterAddress = Guid.NewGuid().ToString(),
 | ||
|                 };
 | ||
|                 foreach (var item in variable)
 | ||
|                 {
 | ||
|                     sourVars.AddVariable(item);
 | ||
|                 }
 | ||
|                 dataResult.Add(sourVars);
 | ||
|             }
 | ||
| 
 | ||
|             return dataResult;
 | ||
|         }
 | ||
|         else
 | ||
|         {
 | ||
|             return new();
 | ||
|         }
 | ||
| 
 | ||
|     }
 | ||
| 
 | ||
|     /// <inheritdoc/>
 | ||
|     protected override async ValueTask<OperResult<byte[]>> ReadSourceAsync(VariableSourceRead deviceVariableSourceRead, CancellationToken cancellationToken)
 | ||
|     {
 | ||
|         DateTime time = DateTime.Now;
 | ||
|         var addresss = deviceVariableSourceRead.VariableRuntimes.Where(a => !a.RegisterAddress.IsNullOrEmpty()).Select(a => a.RegisterAddress!).ToArray();
 | ||
|         try
 | ||
|         {
 | ||
|             await ReadWriteLock.ReaderLockAsync(cancellationToken).ConfigureAwait(false);
 | ||
| 
 | ||
|             var result = await _plc.ReadJTokenValueAsync(addresss, cancellationToken).ConfigureAwait(false);
 | ||
|             foreach (var data in result)
 | ||
|             {
 | ||
|                 if (!cancellationToken.IsCancellationRequested)
 | ||
|                 {
 | ||
|                     var data1 = deviceVariableSourceRead.VariableRuntimes.Where(a => a.RegisterAddress == data.Item1);
 | ||
| 
 | ||
|                     foreach (var item in data1)
 | ||
|                     {
 | ||
|                         object value;
 | ||
|                         if (data.Item3 is JValue jValue)
 | ||
|                         {
 | ||
|                             value = jValue.Value;
 | ||
|                         }
 | ||
|                         else
 | ||
|                         {
 | ||
|                             value = data.Item3;
 | ||
|                         }
 | ||
| 
 | ||
|                         var isGood = StatusCode.IsGood(data.Item2.StatusCode);
 | ||
|                         if (_driverProperties.SourceTimestampEnable)
 | ||
|                         {
 | ||
|                             time = data.Item2.SourceTimestamp.ToLocalTime();
 | ||
|                         }
 | ||
|                         if (isGood)
 | ||
|                         {
 | ||
|                             item.SetValue(value, time);
 | ||
|                         }
 | ||
|                         else
 | ||
|                         {
 | ||
|                             if (item is VariableRuntime variable && (variable.IsOnline || variable.CollectTime == DateTime.UnixEpoch.ToLocalTime()))
 | ||
|                             {
 | ||
|                                 LogMessage.LogWarning($"OPC quality bad:{Environment.NewLine}{data.Item1}");
 | ||
|                             }
 | ||
|                             item.SetValue(null, time, false);
 | ||
|                             item.VariableSource.LastErrorMessage = data.Item2.StatusCode.ToString();
 | ||
|                         }
 | ||
|                     }
 | ||
|                     LogMessage.Trace($"Change:{Environment.NewLine}{data.Item1} : {data.Item3}");
 | ||
|                 }
 | ||
|             }
 | ||
| 
 | ||
|             return OperResult.CreateSuccessResult<byte[]>(null);
 | ||
|         }
 | ||
|         catch (Exception ex)
 | ||
|         {
 | ||
|             return new OperResult<byte[]>($"ReadSourceAsync {addresss.ToJsonNetString()}:{Environment.NewLine}{ex}");
 | ||
|         }
 | ||
|         finally
 | ||
|         {
 | ||
|         }
 | ||
|     }
 | ||
| 
 | ||
|     /// <inheritdoc/>
 | ||
|     protected override async ValueTask<Dictionary<string, OperResult>> WriteValuesAsync(Dictionary<VariableRuntime, JToken> writeInfoLists, CancellationToken cancellationToken)
 | ||
|     {
 | ||
|         using var writeLock = ReadWriteLock.WriterLock();
 | ||
|         try
 | ||
|         {
 | ||
|             var result = await _plc.WriteNodeAsync(writeInfoLists.ToDictionary(a => a.Key.RegisterAddress!, a => a.Value), cancellationToken).ConfigureAwait(false);
 | ||
|             return result.ToDictionary<KeyValuePair<string, Tuple<bool, string>>, string, OperResult>(a =>
 | ||
|             {
 | ||
|                 return writeInfoLists.Keys.FirstOrDefault(b => b.RegisterAddress == a.Key)?.Name!;
 | ||
|             }
 | ||
|             , a =>
 | ||
|             {
 | ||
|                 if (!a.Value.Item1)
 | ||
|                     return new OperResult(a.Value.Item2);
 | ||
|                 else
 | ||
|                     return OperResult.Success;
 | ||
|             })!;
 | ||
|         }
 | ||
|         finally
 | ||
|         {
 | ||
|         }
 | ||
|     }
 | ||
| 
 | ||
|     private void _plc_LogEvent(byte level, object sender, string message, Exception ex)
 | ||
|     {
 | ||
|         LogMessage?.Log((LogLevel)level, sender, message, ex);
 | ||
|     }
 | ||
| 
 | ||
|     public override async Task AfterVariablesChangedAsync()
 | ||
|     {
 | ||
|         try
 | ||
|         {
 | ||
|             _plc?.Disconnect();
 | ||
|             await base.AfterVariablesChangedAsync().ConfigureAwait(false);
 | ||
|         }
 | ||
|         finally
 | ||
|         {
 | ||
|             VariableAddresDicts = IdVariableRuntimes.Select(a => a.Value).Where(it => !it.RegisterAddress.IsNullOrEmpty()).GroupBy(a => a.RegisterAddress).ToDictionary(a => a.Key!, b => b.ToList());
 | ||
|             await _plc.ConnectAsync(default).ConfigureAwait(false);
 | ||
|         }
 | ||
| 
 | ||
|     }
 | ||
| 
 | ||
|     private Dictionary<string, List<VariableRuntime>> VariableAddresDicts { get; set; } = new();
 | ||
| 
 | ||
|     private void DataChangedHandler((VariableNode variableNode, MonitoredItem monitoredItem, DataValue dataValue, JToken jToken) data)
 | ||
|     {
 | ||
|         DateTime time = DateTime.Now;
 | ||
|         try
 | ||
|         {
 | ||
|             if (CurrentDevice.Pause)
 | ||
|                 return;
 | ||
|             if (_token.IsCancellationRequested)
 | ||
|                 return;
 | ||
| 
 | ||
| 
 | ||
|             if (CurrentDevice.Pause)
 | ||
|             {
 | ||
|                 return;
 | ||
|             }
 | ||
| 
 | ||
|             LogMessage.Trace($"Change: {Environment.NewLine} {data.monitoredItem.StartNodeId} : {data.jToken?.ToString()}");
 | ||
| 
 | ||
|             //尝试固定点位的数据类型
 | ||
|             var type = TypeInfo.GetSystemType(TypeInfo.GetBuiltInType(data.variableNode.DataType, _plc.Session.SystemContext.TypeTable), data.variableNode.ValueRank);
 | ||
| 
 | ||
|             if (!VariableAddresDicts.TryGetValue(data.monitoredItem.StartNodeId.ToString(), out var itemReads)) return;
 | ||
| 
 | ||
|             object value;
 | ||
|             if (data.jToken is JValue jValue)
 | ||
|             {
 | ||
|                 value = jValue.Value;
 | ||
|             }
 | ||
|             else
 | ||
|             {
 | ||
|                 value = data.jToken;
 | ||
|             }
 | ||
|             var isGood = StatusCode.IsGood(data.dataValue.StatusCode);
 | ||
|             if (_driverProperties.SourceTimestampEnable)
 | ||
|             {
 | ||
|                 time = data.dataValue.SourceTimestamp.ToLocalTime();
 | ||
|             }
 | ||
|             foreach (var item in itemReads)
 | ||
|             {
 | ||
|                 if (CurrentDevice.Pause)
 | ||
|                     return;
 | ||
|                 if (_token.IsCancellationRequested)
 | ||
|                     return;
 | ||
|                 if (item.DataType == DataTypeEnum.Object)
 | ||
|                     if (type.Namespace.StartsWith("System"))
 | ||
|                     {
 | ||
|                         var enumValues = Enum.GetValues<DataTypeEnum>();
 | ||
|                         var stringList = enumValues.Select(e => e.ToString());
 | ||
|                         if (stringList.Contains(type.Name))
 | ||
|                             try { item.DataType = Enum.Parse<DataTypeEnum>(type.Name); } catch { }
 | ||
|                     }
 | ||
|                 if (isGood)
 | ||
|                 {
 | ||
|                     item.SetValue(value, time);
 | ||
|                 }
 | ||
|                 else
 | ||
|                 {
 | ||
|                     if ((item.IsOnline || item.CollectTime == DateTime.UnixEpoch.ToLocalTime()))
 | ||
|                     {
 | ||
|                         LogMessage.LogWarning($"OPC quality bad:{Environment.NewLine}{data.Item1}");
 | ||
|                     }
 | ||
|                     item.SetValue(null, time, false);
 | ||
|                     item.VariableSource.LastErrorMessage = data.dataValue.StatusCode.ToString();
 | ||
|                 }
 | ||
|             }
 | ||
|             success = true;
 | ||
|         }
 | ||
|         catch (Exception ex)
 | ||
|         {
 | ||
|             if (success)
 | ||
|                 LogMessage?.LogWarning(ex);
 | ||
|             success = false;
 | ||
|         }
 | ||
|     }
 | ||
| }
 | 
