mirror of
				https://gitee.com/ThingsGateway/ThingsGateway.git
				synced 2025-10-31 07:33:58 +08:00 
			
		
		
		
	pref: 异步状态机优化
This commit is contained in:
		| @@ -15,11 +15,11 @@ public partial class AdminTable<TItem> where TItem : class, new() | |||||||
| { | { | ||||||
|     /// <inheritdoc cref="Table{TItem}.OnColumnVisibleChanged"/> |     /// <inheritdoc cref="Table{TItem}.OnColumnVisibleChanged"/> | ||||||
|     [Parameter] |     [Parameter] | ||||||
|     public Func<string,bool, Task> OnColumnVisibleChanged { get; set; } |     public Func<string, bool, Task> OnColumnVisibleChanged { get; set; } | ||||||
|  |  | ||||||
|     /// <inheritdoc cref="Table{TItem}.OnColumnCreating"/> |     /// <inheritdoc cref="Table{TItem}.OnColumnCreating"/> | ||||||
|     [Parameter] |     [Parameter] | ||||||
|     public Func<List<ITableColumn>,Task> OnColumnCreating { get; set; } |     public Func<List<ITableColumn>, Task> OnColumnCreating { get; set; } | ||||||
|     /// <inheritdoc cref="Table{TItem}.RenderMode"/> |     /// <inheritdoc cref="Table{TItem}.RenderMode"/> | ||||||
|     [Parameter] |     [Parameter] | ||||||
|     public TableRenderMode RenderMode { get; set; } |     public TableRenderMode RenderMode { get; set; } | ||||||
|   | |||||||
| @@ -118,7 +118,7 @@ public class ObjectPoolLock<T> : DisposeBase, IPool<T> where T : class | |||||||
|                     if (count == 0) Init(); |                     if (count == 0) Init(); | ||||||
|  |  | ||||||
| #if DEBUG | #if DEBUG | ||||||
|                 WriteLog("Acquire Create Free={0} Busy={1}", FreeCount, count + 1); |                     WriteLog("Acquire Create Free={0} Busy={1}", FreeCount, count + 1); | ||||||
| #endif | #endif | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
| @@ -156,7 +156,7 @@ public class ObjectPoolLock<T> : DisposeBase, IPool<T> where T : class | |||||||
|             if (!_busy.Remove(value)) |             if (!_busy.Remove(value)) | ||||||
|             { |             { | ||||||
| #if DEBUG | #if DEBUG | ||||||
|             WriteLog("Return Error"); |                 WriteLog("Return Error"); | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
|                 return false; |                 return false; | ||||||
|   | |||||||
| @@ -8,7 +8,7 @@ | |||||||
| //  QQ群:605534569 | //  QQ群:605534569 | ||||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||||
| 
 | 
 | ||||||
| namespace ThingsGateway.Foundation; | namespace ThingsGateway.NewLife; | ||||||
| 
 | 
 | ||||||
| public class LinkedCancellationTokenSourceCache : IDisposable | public class LinkedCancellationTokenSourceCache : IDisposable | ||||||
| { | { | ||||||
| @@ -63,6 +63,7 @@ public class LinkedCancellationTokenSourceCache : IDisposable | |||||||
|             _cachedCts?.Dispose(); |             _cachedCts?.Dispose(); | ||||||
|             _cachedCts = null!; |             _cachedCts = null!; | ||||||
|         } |         } | ||||||
|  |         GC.SuppressFinalize(this); | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @@ -10,13 +10,18 @@ | |||||||
| //  感谢您的下载和使用 | //  感谢您的下载和使用 | ||||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||||
| 
 | 
 | ||||||
| namespace ThingsGateway.Foundation; | namespace ThingsGateway.NewLife; | ||||||
| 
 | 
 | ||||||
| using System; | using System; | ||||||
| using System.Threading; | using System.Threading; | ||||||
| 
 | 
 | ||||||
| public sealed class ReusableCancellationTokenSource : IDisposable | public sealed class ReusableCancellationTokenSource : IDisposable | ||||||
| { | { | ||||||
|  |     ~ReusableCancellationTokenSource() | ||||||
|  |     { | ||||||
|  |         Dispose(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     private readonly Timer _timer; |     private readonly Timer _timer; | ||||||
|     private CancellationTokenSource? _cts; |     private CancellationTokenSource? _cts; | ||||||
| 
 | 
 | ||||||
| @@ -47,7 +52,7 @@ public sealed class ReusableCancellationTokenSource : IDisposable | |||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// 获取一个 CTS,并启动超时 |     /// 获取一个 CTS,并启动超时 | ||||||
|     /// </summary> |     /// </summary> | ||||||
|     public CancellationTokenSource GetTokenSource(long timeout, CancellationToken external1 = default, CancellationToken external2 = default, CancellationToken external3 = default) |     public CancellationToken GetTokenSource(long timeout, CancellationToken external1 = default, CancellationToken external2 = default, CancellationToken external3 = default) | ||||||
|     { |     { | ||||||
|         TimeoutStatus = false; |         TimeoutStatus = false; | ||||||
| 
 | 
 | ||||||
| @@ -57,7 +62,7 @@ public sealed class ReusableCancellationTokenSource : IDisposable | |||||||
|         // 启动 Timer |         // 启动 Timer | ||||||
|         _timer.Change(timeout, Timeout.Infinite); |         _timer.Change(timeout, Timeout.Infinite); | ||||||
| 
 | 
 | ||||||
|         return _cts; |         return _cts.Token; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| @@ -71,15 +76,16 @@ public sealed class ReusableCancellationTokenSource : IDisposable | |||||||
|     /// </summary> |     /// </summary> | ||||||
|     public void Cancel() |     public void Cancel() | ||||||
|     { |     { | ||||||
|         _cts?.SafeCancel(); |         try { _cts?.Cancel(); } catch { } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public void Dispose() |     public void Dispose() | ||||||
|     { |     { | ||||||
|         _cts?.SafeCancel(); |         try { _cts?.Cancel(); } catch { } | ||||||
|         _cts?.SafeDispose(); |         try { _cts?.Dispose(); } catch { } | ||||||
|         _linkedCtsCache.SafeDispose(); |         try { _linkedCtsCache?.Dispose(); } catch { } | ||||||
|         _timer.SafeDispose(); |         try { _timer?.Dispose(); } catch { } | ||||||
|  |         GC.SuppressFinalize(this); | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @@ -8,6 +8,8 @@ | |||||||
| //  QQ群:605534569 | //  QQ群:605534569 | ||||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||||
|  |  | ||||||
|  | using ThingsGateway.NewLife.Collections; | ||||||
|  |  | ||||||
| namespace ThingsGateway.NewLife; | namespace ThingsGateway.NewLife; | ||||||
|  |  | ||||||
| /// <summary> | /// <summary> | ||||||
| @@ -93,15 +95,20 @@ public sealed class WaitLock : IDisposable | |||||||
| #if NET6_0_OR_GREATER | #if NET6_0_OR_GREATER | ||||||
|         if (cancellationToken.CanBeCanceled) |         if (cancellationToken.CanBeCanceled) | ||||||
|             return WaitUntilCountOrTimeoutAsync(_waiterLock.WaitAsync(Timeout.Infinite, CancellationToken.None), Timeout.Infinite, cancellationToken); |             return WaitUntilCountOrTimeoutAsync(_waiterLock.WaitAsync(Timeout.Infinite, CancellationToken.None), Timeout.Infinite, cancellationToken); | ||||||
|  |         //return WaitUntilAsync2(_waiterLock.WaitAsync(Timeout.Infinite, CancellationToken.None), Timeout.Infinite, cancellationToken); | ||||||
|         else |         else | ||||||
|             return _waiterLock.WaitAsync(Timeout.Infinite, CancellationToken.None); |             return _waiterLock.WaitAsync(Timeout.Infinite, CancellationToken.None); | ||||||
|  |  | ||||||
| #else | #else | ||||||
|         return _waiterLock.WaitAsync(Timeout.Infinite,cancellationToken); |         return _waiterLock.WaitAsync(Timeout.Infinite, cancellationToken); | ||||||
| #endif | #endif | ||||||
|     } |     } | ||||||
|  |  | ||||||
| #if NET6_0_OR_GREATER | #if NET6_0_OR_GREATER | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     //private ObjectPoolLock<ReusableCancellationTokenSource> _reusableTimeouts = new(); | ||||||
|  |  | ||||||
|     /// <summary>Performs the asynchronous wait.</summary> |     /// <summary>Performs the asynchronous wait.</summary> | ||||||
|     /// <param name="asyncWaiter">The asynchronous waiter.</param> |     /// <param name="asyncWaiter">The asynchronous waiter.</param> | ||||||
|     /// <param name="millisecondsTimeout">The timeout.</param> |     /// <param name="millisecondsTimeout">The timeout.</param> | ||||||
| @@ -118,6 +125,61 @@ public sealed class WaitLock : IDisposable | |||||||
|             return (asyncWaiter.WaitAsync(TimeSpan.FromMilliseconds(millisecondsTimeout), cancellationToken)); |             return (asyncWaiter.WaitAsync(TimeSpan.FromMilliseconds(millisecondsTimeout), cancellationToken)); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     //private Task WaitUntilAsync2(Task task, int timeoutMs, CancellationToken token) | ||||||
|  |     //{ | ||||||
|  |     //    if (task.IsCompleted) return task; | ||||||
|  |  | ||||||
|  |     //    var tcs = new TaskCompletionSource<object?>(TaskCreationOptions.RunContinuationsAsynchronously); | ||||||
|  |     //    var reusableTimeout = _reusableTimeouts.Get(); | ||||||
|  |  | ||||||
|  |     //    CancellationTokenRegistration ctr = default; | ||||||
|  |  | ||||||
|  |     //    // 超时 + 取消 Token | ||||||
|  |     //    if (timeoutMs != Timeout.Infinite || token.CanBeCanceled) | ||||||
|  |     //    { | ||||||
|  |     //        var ctsToken = reusableTimeout.GetTokenSource(timeoutMs, token); | ||||||
|  |  | ||||||
|  |     //        ctr = ctsToken.Register(static (state, token2) => | ||||||
|  |     //        { | ||||||
|  |     //            var (tcs2, ctoken) = ((TaskCompletionSource<object?>, CancellationToken))state!; | ||||||
|  |     //            if (ctoken.IsCancellationRequested) | ||||||
|  |     //                tcs2.TrySetCanceled(ctoken); | ||||||
|  |     //            else | ||||||
|  |     //                tcs2.TrySetException(new TimeoutException("The operation has timed out.")); | ||||||
|  |     //        }, (tcs, token)); | ||||||
|  |     //    } | ||||||
|  |  | ||||||
|  |     //    if (task.IsCompleted) | ||||||
|  |     //    { | ||||||
|  |     //        _reusableTimeouts.Return(reusableTimeout); | ||||||
|  |     //        ctr.Dispose(); | ||||||
|  |     //        return task; | ||||||
|  |     //    } | ||||||
|  |  | ||||||
|  |     //    // 监听原始任务 | ||||||
|  |     //    task.ContinueWith(static (t, state) => | ||||||
|  |     //    { | ||||||
|  |     //        var (tcs2, ctr2, ctsPool, cts) = ((TaskCompletionSource<object?>, CancellationTokenRegistration, ObjectPoolLock<ReusableCancellationTokenSource>, ReusableCancellationTokenSource))state!; | ||||||
|  |     //        try | ||||||
|  |     //        { | ||||||
|  |     //            if (t.IsCanceled) | ||||||
|  |     //                tcs2.TrySetCanceled(); | ||||||
|  |     //            else if (t.IsFaulted) | ||||||
|  |     //                tcs2.TrySetException(t.Exception!.InnerExceptions); | ||||||
|  |     //            else | ||||||
|  |     //                tcs2.TrySetResult(null); | ||||||
|  |     //        } | ||||||
|  |     //        finally | ||||||
|  |     //        { | ||||||
|  |     //            ctsPool.Return(cts); | ||||||
|  |     //            ctr2.Dispose(); | ||||||
|  |     //        } | ||||||
|  |     //    }, (tcs, ctr, _reusableTimeouts, reusableTimeout), CancellationToken.None, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default); | ||||||
|  |  | ||||||
|  |     //    return tcs.Task; | ||||||
|  |     //} | ||||||
|  |  | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
|     /// <summary> |     /// <summary> | ||||||
| @@ -132,7 +194,7 @@ public sealed class WaitLock : IDisposable | |||||||
|             return _waiterLock.WaitAsync(Timeout.Infinite, CancellationToken.None); |             return _waiterLock.WaitAsync(Timeout.Infinite, CancellationToken.None); | ||||||
|  |  | ||||||
| #else | #else | ||||||
|         return _waiterLock.WaitAsync(millisecondsTimeout,cancellationToken); |         return _waiterLock.WaitAsync(millisecondsTimeout, cancellationToken); | ||||||
| #endif | #endif | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -140,6 +202,9 @@ public sealed class WaitLock : IDisposable | |||||||
|     public void Dispose() |     public void Dispose() | ||||||
|     { |     { | ||||||
|         DisposedValue = true; |         DisposedValue = true; | ||||||
|  | #if NET6_0_OR_GREATER | ||||||
|  |         //_reusableTimeouts?.TryDispose(); | ||||||
|  | #endif | ||||||
|         _waiterLock?.TryDispose(); |         _waiterLock?.TryDispose(); | ||||||
|         GC.SuppressFinalize(this); |         GC.SuppressFinalize(this); | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -56,7 +56,8 @@ namespace PooledAwait.Internal | |||||||
|         /// <summary>Whether the current operation has completed.</summary> |         /// <summary>Whether the current operation has completed.</summary> | ||||||
|         private bool _completed; |         private bool _completed; | ||||||
|         /// <summary>The result with which the operation succeeded, or the default value if it hasn't yet completed or failed.</summary> |         /// <summary>The result with which the operation succeeded, or the default value if it hasn't yet completed or failed.</summary> | ||||||
|         /* [AllowNull, MaybeNull] */ private TResult _result; |         /* [AllowNull, MaybeNull] */ | ||||||
|  |         private TResult _result; | ||||||
|         /// <summary>The exception with which the operation failed, or null if it hasn't yet completed or completed successfully.</summary> |         /// <summary>The exception with which the operation failed, or null if it hasn't yet completed or completed successfully.</summary> | ||||||
|         private ExceptionDispatchInfo? _error; |         private ExceptionDispatchInfo? _error; | ||||||
|         /// <summary>The current version of this value, used to help prevent misuse.</summary> |         /// <summary>The current version of this value, used to help prevent misuse.</summary> | ||||||
|   | |||||||
| @@ -1,6 +1,5 @@ | |||||||
| using System.Data; | using System.Data; | ||||||
| using System.Data.Common; | using System.Data.Common; | ||||||
| using System.Numerics; |  | ||||||
|  |  | ||||||
| using ThingsGateway.NewLife.Reflection; | using ThingsGateway.NewLife.Reflection; | ||||||
| namespace ThingsGateway.SqlSugar | namespace ThingsGateway.SqlSugar | ||||||
|   | |||||||
| @@ -1,9 +1,9 @@ | |||||||
| <Project> | <Project> | ||||||
|  |  | ||||||
| 	<PropertyGroup> | 	<PropertyGroup> | ||||||
| 		<PluginVersion>10.11.111</PluginVersion> | 		<PluginVersion>10.11.114</PluginVersion> | ||||||
| 		<ProPluginVersion>10.11.111</ProPluginVersion> | 		<ProPluginVersion>10.11.114</ProPluginVersion> | ||||||
| 		<DefaultVersion>10.11.111</DefaultVersion> | 		<DefaultVersion>10.11.114</DefaultVersion> | ||||||
| 		<AuthenticationVersion>10.11.6</AuthenticationVersion> | 		<AuthenticationVersion>10.11.6</AuthenticationVersion> | ||||||
| 		<SourceGeneratorVersion>10.11.6</SourceGeneratorVersion> | 		<SourceGeneratorVersion>10.11.6</SourceGeneratorVersion> | ||||||
| 		<NET8Version>8.0.21</NET8Version> | 		<NET8Version>8.0.21</NET8Version> | ||||||
| @@ -12,7 +12,7 @@ | |||||||
| 		<IsTrimmable>false</IsTrimmable> | 		<IsTrimmable>false</IsTrimmable> | ||||||
| 		<ManagementProPluginVersion>10.11.87</ManagementProPluginVersion> | 		<ManagementProPluginVersion>10.11.87</ManagementProPluginVersion> | ||||||
| 		<ManagementPluginVersion>10.11.87</ManagementPluginVersion> | 		<ManagementPluginVersion>10.11.87</ManagementPluginVersion> | ||||||
| 		<TSVersion>4.0.0-beta.135</TSVersion> | 		<TSVersion>4.0.0-beta.140</TSVersion> | ||||||
| 		 | 		 | ||||||
| 		 | 		 | ||||||
| 	</PropertyGroup> | 	</PropertyGroup> | ||||||
|   | |||||||
| @@ -11,6 +11,8 @@ | |||||||
| using Newtonsoft.Json; | using Newtonsoft.Json; | ||||||
| using Newtonsoft.Json.Linq; | using Newtonsoft.Json.Linq; | ||||||
|  |  | ||||||
|  | using PooledAwait; | ||||||
|  |  | ||||||
| using System.Linq.Expressions; | using System.Linq.Expressions; | ||||||
|  |  | ||||||
| using ThingsGateway.Gateway.Application.Extensions; | using ThingsGateway.Gateway.Application.Extensions; | ||||||
| @@ -139,18 +141,27 @@ public abstract class VariableObject | |||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// <see cref="VariableRuntimeAttribute"/>特性连读,反射赋值到继承类中的属性 |     /// <see cref="VariableRuntimeAttribute"/>特性连读,反射赋值到继承类中的属性 | ||||||
|     /// </summary> |     /// </summary> | ||||||
|     public virtual async ValueTask<OperResult> MultiReadAsync(CancellationToken cancellationToken = default) |     public virtual ValueTask<OperResult> MultiReadAsync(CancellationToken cancellationToken = default) | ||||||
|     { |     { | ||||||
|         try |         try | ||||||
|         { |         { | ||||||
|             GetVariableSources(); |             GetVariableSources(); | ||||||
|             //连读 |             //连读 | ||||||
|             foreach (var item in DeviceVariableSourceReads) |             return MultiReadAsync(this, cancellationToken); | ||||||
|  |         } | ||||||
|  |         catch (Exception ex) | ||||||
|  |         { | ||||||
|  |             return EasyValueTask.FromResult(new OperResult(ex)); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         static async PooledValueTask<OperResult> MultiReadAsync(VariableObject @this, CancellationToken cancellationToken) | ||||||
|  |         { | ||||||
|  |             foreach (var item in @this.DeviceVariableSourceReads) | ||||||
|             { |             { | ||||||
|                 var result = await Device.ReadAsync(item.RegisterAddress, item.Length, cancellationToken).ConfigureAwait(false); |                 var result = await @this.Device.ReadAsync(item.RegisterAddress, item.Length, cancellationToken).ConfigureAwait(false); | ||||||
|                 if (result.IsSuccess) |                 if (result.IsSuccess) | ||||||
|                 { |                 { | ||||||
|                     var result1 = item.VariableRuntimes.PraseStructContent(Device, result.Content.Span, exWhenAny: true); |                     var result1 = item.VariableRuntimes.PraseStructContent(@this.Device, result.Content.Span, exWhenAny: true); | ||||||
|                     if (!result1.IsSuccess) |                     if (!result1.IsSuccess) | ||||||
|                     { |                     { | ||||||
|                         item.LastErrorMessage = result1.ErrorMessage; |                         item.LastErrorMessage = result1.ErrorMessage; | ||||||
| @@ -168,13 +179,9 @@ public abstract class VariableObject | |||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|  |  | ||||||
|             SetValue(); |             @this.SetValue(); | ||||||
|             return OperResult.Success; |             return OperResult.Success; | ||||||
|         } |         } | ||||||
|         catch (Exception ex) |  | ||||||
|         { |  | ||||||
|             return new OperResult(ex); |  | ||||||
|         } |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /// <summary> |     /// <summary> | ||||||
| @@ -205,30 +212,31 @@ public abstract class VariableObject | |||||||
|     /// <param name="propertyName">属性名称,必须使用<see cref="VariableRuntimeAttribute"/>特性</param> |     /// <param name="propertyName">属性名称,必须使用<see cref="VariableRuntimeAttribute"/>特性</param> | ||||||
|     /// <param name="value">写入值</param> |     /// <param name="value">写入值</param> | ||||||
|     /// <param name="cancellationToken">取消令箭</param> |     /// <param name="cancellationToken">取消令箭</param> | ||||||
|     public virtual async ValueTask<OperResult> WriteValueAsync(string propertyName, object value, CancellationToken cancellationToken = default) |     public virtual ValueTask<OperResult> WriteValueAsync(string propertyName, object value, CancellationToken cancellationToken = default) | ||||||
|     { |     { | ||||||
|         try |         try | ||||||
|         { |         { | ||||||
|             GetVariableSources(); |             GetVariableSources(); | ||||||
|             if (string.IsNullOrEmpty(propertyName)) |             if (string.IsNullOrEmpty(propertyName)) | ||||||
|             { |             { | ||||||
|                 return new OperResult($"PropertyName cannot be null or empty."); |                 return EasyValueTask.FromResult(new OperResult($"PropertyName cannot be null or empty.")); | ||||||
|             } |             } | ||||||
|  |  | ||||||
|             if (!VariableRuntimePropertyDict.TryGetValue(propertyName, out var variableRuntimeProperty)) |             if (!VariableRuntimePropertyDict.TryGetValue(propertyName, out var variableRuntimeProperty)) | ||||||
|             { |             { | ||||||
|                 return new OperResult($"This attribute is not recognized and may not have been identified using the {typeof(VariableRuntimeAttribute)} attribute"); |                 return EasyValueTask.FromResult(new OperResult($"This attribute is not recognized and may not have been identified using the {typeof(VariableRuntimeAttribute)} attribute")); | ||||||
|             } |             } | ||||||
|  |  | ||||||
|             JToken jToken = GetExpressionsValue(value, variableRuntimeProperty); |             JToken jToken = GetExpressionsValue(value, variableRuntimeProperty); | ||||||
|  |  | ||||||
|             var result = await Device.WriteJTokenAsync(variableRuntimeProperty.VariableClass.RegisterAddress, jToken, variableRuntimeProperty.VariableClass.DataType, cancellationToken).ConfigureAwait(false); |             return Device.WriteJTokenAsync(variableRuntimeProperty.VariableClass.RegisterAddress, jToken, variableRuntimeProperty.VariableClass.DataType, cancellationToken); | ||||||
|             return result; |  | ||||||
|         } |         } | ||||||
|         catch (Exception ex) |         catch (Exception ex) | ||||||
|         { |         { | ||||||
|             return new OperResult(ex); |             return EasyValueTask.FromResult(new OperResult(ex)); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /// <summary> |     /// <summary> | ||||||
|   | |||||||
| @@ -8,6 +8,8 @@ | |||||||
| //  QQ群:605534569 | //  QQ群:605534569 | ||||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||||
|  |  | ||||||
|  | using PooledAwait; | ||||||
|  |  | ||||||
| using System.Runtime.CompilerServices; | using System.Runtime.CompilerServices; | ||||||
|  |  | ||||||
| using TouchSocket.Resources; | using TouchSocket.Resources; | ||||||
| @@ -138,82 +140,86 @@ public class DDPTcpSessionClientChannel : TcpSessionClientChannel | |||||||
|  |  | ||||||
|     private DeviceSingleStreamDataHandleAdapter<DDPTcpMessage> DDPAdapter = new(); |     private DeviceSingleStreamDataHandleAdapter<DDPTcpMessage> DDPAdapter = new(); | ||||||
|  |  | ||||||
|     protected override async ValueTask<bool> OnTcpReceiving(IBytesReader byteBlock) |     protected override ValueTask<bool> OnTcpReceiving(IBytesReader byteBlock) | ||||||
|     { |     { | ||||||
|  |  | ||||||
|         if (DDPAdapter.TryParseRequest(ref byteBlock, out var message)) |         if (DDPAdapter.TryParseRequest(ref byteBlock, out var message)) | ||||||
|         { |         { | ||||||
|             return true; |             return EasyValueTask.FromResult(true); | ||||||
|         } |         } | ||||||
|  |         return OnTcpReceiving(this, message); | ||||||
|  |  | ||||||
|         if (message != null) |         static async PooledValueTask<bool> OnTcpReceiving(DDPTcpSessionClientChannel @this, DDPTcpMessage message) | ||||||
|         { |         { | ||||||
|             if (message.IsSuccess) |             if (message != null) | ||||||
|             { |             { | ||||||
|                 var id = $"ID={message.Id}"; |                 if (message.IsSuccess) | ||||||
|                 if (message.Type == 0x09) |  | ||||||
|                 { |                 { | ||||||
|                     var reader = new ClassBytesReader(message.Content); |                     var id = $"ID={message.Id}"; | ||||||
|  |                     if (message.Type == 0x09) | ||||||
|                     if (this.DataHandlingAdapter == null) |  | ||||||
|                     { |                     { | ||||||
|                         await this.OnTcpReceived(new ReceivedDataEventArgs(message.Content, default)).ConfigureAwait(false); |                         var reader = new ClassBytesReader(message.Content); | ||||||
|  |  | ||||||
|  |                         if (@this.DataHandlingAdapter == null) | ||||||
|  |                         { | ||||||
|  |                             await @this.OnTcpReceived(new ReceivedDataEventArgs(message.Content, default)).ConfigureAwait(false); | ||||||
|  |                         } | ||||||
|  |                         else | ||||||
|  |                         { | ||||||
|  |                             await @this.DataHandlingAdapter.ReceivedInputAsync(reader).ConfigureAwait(false); | ||||||
|  |                         } | ||||||
|  |  | ||||||
|  |                         return true; | ||||||
|                     } |                     } | ||||||
|                     else |                     else | ||||||
|                     { |                     { | ||||||
|                         await this.DataHandlingAdapter.ReceivedInputAsync(reader).ConfigureAwait(false); |                         if (message.Type == 0x01) | ||||||
|                     } |  | ||||||
|  |  | ||||||
|                     return true; |  | ||||||
|                 } |  | ||||||
|                 else |  | ||||||
|                 { |  | ||||||
|                     if (message.Type == 0x01) |  | ||||||
|                     { |  | ||||||
|                         bool log = false; |  | ||||||
|                         if (id != Id) log = true; |  | ||||||
|  |  | ||||||
|                         //注册ID |  | ||||||
|                         if (Service is ITcpServiceChannel tcpService && tcpService.TryGetClient(id, out var oldClient) && oldClient != this) |  | ||||||
|                         { |                         { | ||||||
|                             Logger?.Debug($"Old socket connections with the same ID {id} will be closed"); |                             bool log = false; | ||||||
|                             try |                             if (id != @this.Id) log = true; | ||||||
|                             { |  | ||||||
|                                 //await oldClient.ShutdownAsync(System.Net.Sockets.SocketShutdown.Both).ConfigureAwait(false); |                             //注册ID | ||||||
|                                 await oldClient.CloseAsync().ConfigureAwait(false); |                             if (@this.Service is ITcpServiceChannel tcpService && tcpService.TryGetClient(id, out var oldClient) && oldClient != @this) | ||||||
|                             } |  | ||||||
|                             catch |  | ||||||
|                             { |  | ||||||
|                             } |  | ||||||
|                             try |  | ||||||
|                             { |  | ||||||
|                                 oldClient.Dispose(); |  | ||||||
|                             } |  | ||||||
|                             catch |  | ||||||
|                             { |                             { | ||||||
|  |                                 @this.Logger?.Debug($"Old socket connections with the same ID {id} will be closed"); | ||||||
|  |                                 try | ||||||
|  |                                 { | ||||||
|  |                                     //await oldClient.ShutdownAsync(System.Net.Sockets.SocketShutdown.Both).ConfigureAwait(false); | ||||||
|  |                                     await oldClient.CloseAsync().ConfigureAwait(false); | ||||||
|  |                                 } | ||||||
|  |                                 catch | ||||||
|  |                                 { | ||||||
|  |                                 } | ||||||
|  |                                 try | ||||||
|  |                                 { | ||||||
|  |                                     oldClient.Dispose(); | ||||||
|  |                                 } | ||||||
|  |                                 catch | ||||||
|  |                                 { | ||||||
|  |                                 } | ||||||
|                             } |                             } | ||||||
|  |  | ||||||
|  |                             await @this.ResetIdAsync(id, @this.ClosedToken).ConfigureAwait(false); | ||||||
|  |  | ||||||
|  |                             //发送成功 | ||||||
|  |                             await @this.ProtectedSendAsync(new DDPSend(ReadOnlyMemory<byte>.Empty, id, true, 0x81), @this.ClosedToken).ConfigureAwait(false); | ||||||
|  |                             if (log) | ||||||
|  |                                 @this.Logger?.Info(string.Format(AppResource.DtuConnected, @this.Id)); | ||||||
|  |                         } | ||||||
|  |                         else if (message.Type == 0x02) | ||||||
|  |                         { | ||||||
|  |                             await @this.ProtectedSendAsync(new DDPSend(ReadOnlyMemory<byte>.Empty, @this.Id, true, 0x82), @this.ClosedToken).ConfigureAwait(false); | ||||||
|  |                             @this.Logger?.Info(string.Format(AppResource.DtuDisconnecting, @this.Id)); | ||||||
|  |                             await Task.Delay(100).ConfigureAwait(false); | ||||||
|  |                             await @this.CloseAsync().ConfigureAwait(false); | ||||||
|  |                             @this.SafeDispose(); | ||||||
|                         } |                         } | ||||||
|  |  | ||||||
|                         await ResetIdAsync(id, ClosedToken).ConfigureAwait(false); |  | ||||||
|  |  | ||||||
|                         //发送成功 |  | ||||||
|                         await base.ProtectedSendAsync(new DDPSend(ReadOnlyMemory<byte>.Empty, id, true, 0x81), ClosedToken).ConfigureAwait(false); |  | ||||||
|                         if (log) |  | ||||||
|                             Logger?.Info(string.Format(AppResource.DtuConnected, Id)); |  | ||||||
|                     } |  | ||||||
|                     else if (message.Type == 0x02) |  | ||||||
|                     { |  | ||||||
|                         await base.ProtectedSendAsync(new DDPSend(ReadOnlyMemory<byte>.Empty, Id, true, 0x82), ClosedToken).ConfigureAwait(false); |  | ||||||
|                         Logger?.Info(string.Format(AppResource.DtuDisconnecting, Id)); |  | ||||||
|                         await Task.Delay(100).ConfigureAwait(false); |  | ||||||
|                         await this.CloseAsync().ConfigureAwait(false); |  | ||||||
|                         this.SafeDispose(); |  | ||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|         } |  | ||||||
|  |  | ||||||
|         return true; |             return true; | ||||||
|  |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     #region Throw |     #region Throw | ||||||
|   | |||||||
| @@ -8,6 +8,8 @@ | |||||||
| //  QQ群:605534569 | //  QQ群:605534569 | ||||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||||
|  |  | ||||||
|  | using PooledAwait; | ||||||
|  |  | ||||||
| using System.Collections.Concurrent; | using System.Collections.Concurrent; | ||||||
| using System.Net; | using System.Net; | ||||||
| using System.Runtime.CompilerServices; | using System.Runtime.CompilerServices; | ||||||
| @@ -80,73 +82,78 @@ public class DDPUdpSessionChannel : UdpSessionChannel, IClientChannel, IDtuUdpSe | |||||||
|     } |     } | ||||||
|  |  | ||||||
|  |  | ||||||
|     protected override async ValueTask<bool> OnUdpReceiving(UdpReceiveingEventArgs e) |     protected override ValueTask<bool> OnUdpReceiving(UdpReceiveingEventArgs e) | ||||||
|     { |     { | ||||||
|         var byteBlock = e.Memory; |         var byteBlock = e.Memory; | ||||||
|         var endPoint = e.EndPoint; |         var endPoint = e.EndPoint; | ||||||
|  |  | ||||||
|         if (!DDPAdapter.TryParseRequest(endPoint, byteBlock, out var message)) |         if (!DDPAdapter.TryParseRequest(endPoint, byteBlock, out var message)) | ||||||
|             return true; |             return EasyValueTask.FromResult(true); | ||||||
|  |  | ||||||
|         if (message != null) |         return OnUdpReceiving(this, endPoint, message); | ||||||
|  |  | ||||||
|  |         static async PooledValueTask<bool> OnUdpReceiving(DDPUdpSessionChannel @this, EndPoint endPoint, DDPUdpMessage message) | ||||||
|         { |         { | ||||||
|             if (message.IsSuccess) |             if (message != null) | ||||||
|             { |             { | ||||||
|                 var id = $"ID={message.Id}"; |                 if (message.IsSuccess) | ||||||
|                 if (message.Type == 0x09) |  | ||||||
|                 { |                 { | ||||||
|                     if (this.DataHandlingAdapter == null) |                     var id = $"ID={message.Id}"; | ||||||
|  |                     if (message.Type == 0x09) | ||||||
|                     { |                     { | ||||||
|                         await this.OnUdpReceived(new UdpReceivedDataEventArgs(endPoint, message.Content, default)).ConfigureAwait(false); |                         if (@this.DataHandlingAdapter == null) | ||||||
|  |                         { | ||||||
|  |                             await @this.OnUdpReceived(new UdpReceivedDataEventArgs(endPoint, message.Content, default)).ConfigureAwait(false); | ||||||
|  |                         } | ||||||
|  |                         else | ||||||
|  |                         { | ||||||
|  |                             await @this.DataHandlingAdapter.ReceivedInputAsync(endPoint, message.Content).ConfigureAwait(false); | ||||||
|  |                         } | ||||||
|  |  | ||||||
|  |                         return true; | ||||||
|                     } |                     } | ||||||
|                     else |                     else | ||||||
|                     { |                     { | ||||||
|                         await this.DataHandlingAdapter.ReceivedInputAsync(endPoint, message.Content).ConfigureAwait(false); |                         if (message.Type == 0x01) | ||||||
|                     } |                         { | ||||||
|  |                             bool log = false; | ||||||
|  |  | ||||||
|                     return true; |                             //注册ID | ||||||
|                 } |                             if (!@this.IdDict.TryAdd(endPoint, id)) | ||||||
|                 else |                             { | ||||||
|                 { |                                 @this.IdDict[endPoint] = id; | ||||||
|                     if (message.Type == 0x01) |                             } | ||||||
|                     { |                             else | ||||||
|                         bool log = false; |                             { | ||||||
|  |                                 log = true; | ||||||
|  |                             } | ||||||
|  |                             if (!@this.EndPointDcit.TryAdd(id, endPoint)) | ||||||
|  |                             { | ||||||
|  |                                 @this.EndPointDcit[id] = endPoint; | ||||||
|  |                             } | ||||||
|  |                             else | ||||||
|  |                             { | ||||||
|  |                                 log = true; | ||||||
|  |                             } | ||||||
|  |  | ||||||
|                         //注册ID |                             //发送成功 | ||||||
|                         if (!IdDict.TryAdd(endPoint, id)) |                             await @this.DDPAdapter.SendInputAsync(endPoint, new DDPSend(ReadOnlyMemory<byte>.Empty, id, false, 0x81), @this.ClosedToken).ConfigureAwait(false); | ||||||
|                         { |                             if (log) | ||||||
|                             IdDict[endPoint] = id; |                                 @this.Logger?.Info(string.Format(AppResource.DtuConnected, id)); | ||||||
|                         } |                         } | ||||||
|                         else |                         else if (message.Type == 0x02) | ||||||
|                         { |                         { | ||||||
|                             log = true; |                             await @this.DDPAdapter.SendInputAsync(endPoint, new DDPSend(ReadOnlyMemory<byte>.Empty, id, false, 0x82), @this.ClosedToken).ConfigureAwait(false); | ||||||
|  |                             @this.Logger?.Info(string.Format(AppResource.DtuDisconnecting, id)); | ||||||
|  |                             await Task.Delay(100).ConfigureAwait(false); | ||||||
|  |                             @this.IdDict.TryRemove(endPoint, out _); | ||||||
|  |                             @this.EndPointDcit.TryRemove(id, out _); | ||||||
|                         } |                         } | ||||||
|                         if (!EndPointDcit.TryAdd(id, endPoint)) |  | ||||||
|                         { |  | ||||||
|                             EndPointDcit[id] = endPoint; |  | ||||||
|                         } |  | ||||||
|                         else |  | ||||||
|                         { |  | ||||||
|                             log = true; |  | ||||||
|                         } |  | ||||||
|  |  | ||||||
|                         //发送成功 |  | ||||||
|                         await DDPAdapter.SendInputAsync(endPoint, new DDPSend(ReadOnlyMemory<byte>.Empty, id, false, 0x81), ClosedToken).ConfigureAwait(false); |  | ||||||
|                         if (log) |  | ||||||
|                             Logger?.Info(string.Format(AppResource.DtuConnected, id)); |  | ||||||
|                     } |  | ||||||
|                     else if (message.Type == 0x02) |  | ||||||
|                     { |  | ||||||
|                         await DDPAdapter.SendInputAsync(endPoint, new DDPSend(ReadOnlyMemory<byte>.Empty, id, false, 0x82), ClosedToken).ConfigureAwait(false); |  | ||||||
|                         Logger?.Info(string.Format(AppResource.DtuDisconnecting, id)); |  | ||||||
|                         await Task.Delay(100).ConfigureAwait(false); |  | ||||||
|                         IdDict.TryRemove(endPoint, out _); |  | ||||||
|                         EndPointDcit.TryRemove(id, out _); |  | ||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|  |             return true; | ||||||
|         } |         } | ||||||
|         return true; |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     #region Throw |     #region Throw | ||||||
|   | |||||||
| @@ -8,6 +8,8 @@ | |||||||
| //  QQ群:605534569 | //  QQ群:605534569 | ||||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||||
|  |  | ||||||
|  | using PooledAwait; | ||||||
|  |  | ||||||
| using System.Text; | using System.Text; | ||||||
|  |  | ||||||
| using ThingsGateway.Foundation.Extension.String; | using ThingsGateway.Foundation.Extension.String; | ||||||
| @@ -59,64 +61,71 @@ public class DtuPlugin : PluginBase, ITcpReceivingPlugin | |||||||
|     public bool DtuIdHex { get; set; } |     public bool DtuIdHex { get; set; } | ||||||
|  |  | ||||||
|     /// <inheritdoc/> |     /// <inheritdoc/> | ||||||
|     public async Task OnTcpReceiving(ITcpSession client, BytesReaderEventArgs e) |     public Task OnTcpReceiving(ITcpSession client, BytesReaderEventArgs e) | ||||||
|     { |     { | ||||||
|         var len = HeartbeatByte.Length; |         return OnTcpReceiving(this, client, e); | ||||||
|         if (client is TcpSessionClientChannel socket && socket.Service is ITcpServiceChannel tcpServiceChannel) |  | ||||||
|  |  | ||||||
|  |         static async PooledTask OnTcpReceiving(DtuPlugin @this, ITcpSession client, BytesReaderEventArgs e) | ||||||
|         { |         { | ||||||
|             if (!socket.Id.StartsWith("ID=")) |             var len = @this.HeartbeatByte.Length; | ||||||
|  |             if (client is TcpSessionClientChannel socket && socket.Service is ITcpServiceChannel tcpServiceChannel) | ||||||
|             { |             { | ||||||
|                 var id = DtuIdHex ? $"ID={e.Reader.ToHexString()}" : $"ID={e.Reader.TotalSequence.ToString(Encoding.UTF8)}"; |                 if (!socket.Id.StartsWith("ID=")) | ||||||
|                 if (tcpServiceChannel.TryGetClient(id, out var oldClient)) |                 { | ||||||
|  |                     var id = @this.DtuIdHex ? $"ID={e.Reader.ToHexString()}" : $"ID={e.Reader.TotalSequence.ToString(Encoding.UTF8)}"; | ||||||
|  |                     if (tcpServiceChannel.TryGetClient(id, out var oldClient)) | ||||||
|  |                     { | ||||||
|  |                         try | ||||||
|  |                         { | ||||||
|  |                             await oldClient.CloseAsync().ConfigureAwait(false); | ||||||
|  |                             oldClient.Dispose(); | ||||||
|  |                         } | ||||||
|  |                         catch | ||||||
|  |                         { | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                     await socket.ResetIdAsync(id, client.ClosedToken).ConfigureAwait(false); | ||||||
|  |                     client.Logger?.Info(string.Format(AppResource.DtuConnected, id)); | ||||||
|  |                     e.Reader.Advance((int)e.Reader.BytesRemaining); | ||||||
|  |                     e.Handled = true; | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 if (!socket.Service.ClientExists(socket.Id)) | ||||||
|                 { |                 { | ||||||
|                     try |                     try | ||||||
|                     { |                     { | ||||||
|                         await oldClient.CloseAsync().ConfigureAwait(false); |                         await socket.CloseAsync().ConfigureAwait(false); | ||||||
|                         oldClient.Dispose(); |                         socket.Dispose(); | ||||||
|                     } |                     } | ||||||
|                     catch |                     catch | ||||||
|                     { |                     { | ||||||
|                     } |                     } | ||||||
|                 } |  | ||||||
|                 await socket.ResetIdAsync(id, client.ClosedToken).ConfigureAwait(false); |  | ||||||
|                 client.Logger?.Info(string.Format(AppResource.DtuConnected, id)); |  | ||||||
|                 e.Reader.Advance((int)e.Reader.BytesRemaining); |  | ||||||
|                 e.Handled = true; |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             if (!socket.Service.ClientExists(socket.Id)) |                     await e.InvokeNext().ConfigureAwait(false);//如果本插件无法处理当前数据,请将数据转至下一个插件。 | ||||||
|             { |                     return; | ||||||
|                 try |  | ||||||
|                 { |  | ||||||
|                     await socket.CloseAsync().ConfigureAwait(false); |  | ||||||
|                     socket.Dispose(); |  | ||||||
|                 } |  | ||||||
|                 catch |  | ||||||
|                 { |  | ||||||
|                 } |                 } | ||||||
|  |  | ||||||
|                 await e.InvokeNext().ConfigureAwait(false);//如果本插件无法处理当前数据,请将数据转至下一个插件。 |                 if (len > 0) | ||||||
|                 return; |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             if (len > 0) |  | ||||||
|             { |  | ||||||
|                 if (HeartbeatByte.Span.SequenceEqual(e.Reader.TotalSequence.Slice(0, (int)Math.Min(len, e.Reader.BytesRemaining + e.Reader.BytesRead)).First.Span)) |  | ||||||
|                 { |                 { | ||||||
|                     if (DateTimeOffset.Now - socket.LastSentTime < TimeSpan.FromMilliseconds(200)) |                     if (@this.HeartbeatByte.Span.SequenceEqual(e.Reader.TotalSequence.Slice(0, (int)Math.Min(len, e.Reader.BytesRemaining + e.Reader.BytesRead)).First.Span)) | ||||||
|                     { |                     { | ||||||
|                         await Task.Delay(200, client.ClosedToken).ConfigureAwait(false); |                         if (DateTimeOffset.Now - socket.LastSentTime < TimeSpan.FromMilliseconds(200)) | ||||||
|  |                         { | ||||||
|  |                             await Task.Delay(200, client.ClosedToken).ConfigureAwait(false); | ||||||
|  |                         } | ||||||
|  |                         //回应心跳包 | ||||||
|  |                         await socket.SendAsync(@this.HeartbeatByte, socket.ClosedToken).ConfigureAwait(false); | ||||||
|  |                         e.Reader.Advance((int)Math.Min(len, e.Reader.BytesRemaining)); | ||||||
|  |                         e.Handled = true; | ||||||
|  |                         if (socket.Logger?.LogLevel <= LogLevel.Trace) | ||||||
|  |                             socket.Logger?.Trace($"{socket}- Heartbeat"); | ||||||
|                     } |                     } | ||||||
|                     //回应心跳包 |  | ||||||
|                     await socket.SendAsync(HeartbeatByte, socket.ClosedToken).ConfigureAwait(false); |  | ||||||
|                     e.Reader.Advance((int)Math.Min(len, e.Reader.BytesRemaining)); |  | ||||||
|                     e.Handled = true; |  | ||||||
|                     if (socket.Logger?.LogLevel <= LogLevel.Trace) |  | ||||||
|                         socket.Logger?.Trace($"{socket}- Heartbeat"); |  | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|  |             await e.InvokeNext().ConfigureAwait(false);//如果本插件无法处理当前数据,请将数据转至下一个插件。 | ||||||
|  |             return; | ||||||
|         } |         } | ||||||
|         await e.InvokeNext().ConfigureAwait(false);//如果本插件无法处理当前数据,请将数据转至下一个插件。 |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |  | ||||||
|   | |||||||
| @@ -167,14 +167,14 @@ internal sealed class HeartbeatAndReceivePlugin : PluginBase, ITcpConnectedPlugi | |||||||
|         await e.InvokeNext().ConfigureAwait(false); |         await e.InvokeNext().ConfigureAwait(false); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     public async Task OnTcpReceiving(ITcpSession client, BytesReaderEventArgs e) |     public Task OnTcpReceiving(ITcpSession client, BytesReaderEventArgs e) | ||||||
|     { |     { | ||||||
|         if (client is ITcpSessionClient) |         if (client is ITcpSessionClient) | ||||||
|         { |         { | ||||||
|             return;//此处可判断,如果为服务器,则不用使用心跳。 |             return Task.CompletedTask;//此处可判断,如果为服务器,则不用使用心跳。 | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         if (DtuId.IsNullOrWhiteSpace()) return; |         if (DtuId.IsNullOrWhiteSpace()) return Task.CompletedTask; | ||||||
|  |  | ||||||
|         if (client is ITcpClient tcpClient) |         if (client is ITcpClient tcpClient) | ||||||
|         { |         { | ||||||
| @@ -187,8 +187,9 @@ internal sealed class HeartbeatAndReceivePlugin : PluginBase, ITcpConnectedPlugi | |||||||
|                     e.Handled = true; |                     e.Handled = true; | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|             await e.InvokeNext().ConfigureAwait(false);//如果本插件无法处理当前数据,请将数据转至下一个插件。 |             return e.InvokeNext();//如果本插件无法处理当前数据,请将数据转至下一个插件。 | ||||||
|         } |         } | ||||||
|  |         return Task.CompletedTask; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |  | ||||||
|   | |||||||
| @@ -331,21 +331,25 @@ public abstract class DeviceBase : AsyncAndSyncDisposableObject, IDevice | |||||||
|     } |     } | ||||||
|     public bool AutoConnect { get; protected set; } = true; |     public bool AutoConnect { get; protected set; } = true; | ||||||
|     /// <inheritdoc/> |     /// <inheritdoc/> | ||||||
|     private async Task SendAsync(ISendMessage sendMessage, IClientChannel channel, CancellationToken token = default) |     private Task SendAsync(ISendMessage sendMessage, IClientChannel channel, CancellationToken token = default) | ||||||
|     { |     { | ||||||
|  |         return SendAsync(this, sendMessage, channel, token); | ||||||
|  |  | ||||||
|         if (SendDelayTime != 0) |         static async PooledTask SendAsync(DeviceBase @this, ISendMessage sendMessage, IClientChannel channel, CancellationToken token) | ||||||
|             await Task.Delay(SendDelayTime, token).ConfigureAwait(false); |  | ||||||
|  |  | ||||||
|         if (channel is IDtuUdpSessionChannel udpSession) |  | ||||||
|         { |         { | ||||||
|             EndPoint? endPoint = GetUdpEndpoint(); |             if (@this.SendDelayTime != 0) | ||||||
|             await udpSession.SendAsync(endPoint, sendMessage, token).ConfigureAwait(false); |                 await Task.Delay(@this.SendDelayTime, token).ConfigureAwait(false); | ||||||
|  |  | ||||||
|         } |             if (channel is IDtuUdpSessionChannel udpSession) | ||||||
|         else |             { | ||||||
|         { |                 EndPoint? endPoint = @this.GetUdpEndpoint(); | ||||||
|             await channel.SendAsync(sendMessage, token).ConfigureAwait(false); |                 await udpSession.SendAsync(endPoint, sendMessage, token).ConfigureAwait(false); | ||||||
|  |  | ||||||
|  |             } | ||||||
|  |             else | ||||||
|  |             { | ||||||
|  |                 await channel.SendAsync(sendMessage, token).ConfigureAwait(false); | ||||||
|  |             } | ||||||
|         } |         } | ||||||
|  |  | ||||||
|     } |     } | ||||||
| @@ -365,59 +369,69 @@ public abstract class DeviceBase : AsyncAndSyncDisposableObject, IDevice | |||||||
|  |  | ||||||
|     private WaitLock connectWaitLock = new(nameof(DeviceBase)); |     private WaitLock connectWaitLock = new(nameof(DeviceBase)); | ||||||
|  |  | ||||||
|     public async ValueTask ConnectAsync(CancellationToken token) |     public ValueTask ConnectAsync(CancellationToken token) | ||||||
|     { |     { | ||||||
|         if (AutoConnect && Channel != null && Channel?.Online != true) |         return ConnectAsync(this, token); | ||||||
|  |  | ||||||
|  |         static async PooledValueTask ConnectAsync(DeviceBase @this, CancellationToken token) | ||||||
|         { |         { | ||||||
|             try |             if (@this.AutoConnect && @this.Channel != null && @this.Channel?.Online != true) | ||||||
|             { |             { | ||||||
|                 await connectWaitLock.WaitAsync(token).ConfigureAwait(false); |                 try | ||||||
|                 if (AutoConnect && Channel != null && Channel?.Online != true) |  | ||||||
|                 { |                 { | ||||||
|                     if (Channel.PluginManager == null) |                     await @this.connectWaitLock.WaitAsync(token).ConfigureAwait(false); | ||||||
|                         await Channel.SetupAsync(Channel.Config.Clone()).ConfigureAwait(false); |                     if (@this.AutoConnect && @this.Channel != null && @this.Channel?.Online != true) | ||||||
|                     await Channel.CloseAsync().ConfigureAwait(false); |                     { | ||||||
|                     using var ctsTime = new CancellationTokenSource(Channel.ChannelOptions.ConnectTimeout); |                         if (@this.Channel.PluginManager == null) | ||||||
|                     using var cts = CancellationTokenSource.CreateLinkedTokenSource(ctsTime.Token, token); |                             await @this.Channel.SetupAsync(@this.Channel.Config.Clone()).ConfigureAwait(false); | ||||||
|                     await Channel.ConnectAsync(cts.Token).ConfigureAwait(false); |                         await @this.Channel.CloseAsync().ConfigureAwait(false); | ||||||
|  |                         using var ctsTime = new CancellationTokenSource(@this.Channel.ChannelOptions.ConnectTimeout); | ||||||
|  |                         using var cts = CancellationTokenSource.CreateLinkedTokenSource(ctsTime.Token, token); | ||||||
|  |                         await @this.Channel.ConnectAsync(cts.Token).ConfigureAwait(false); | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |                 finally | ||||||
|  |                 { | ||||||
|  |                     @this.connectWaitLock.Release(); | ||||||
|                 } |                 } | ||||||
|             } |  | ||||||
|             finally |  | ||||||
|             { |  | ||||||
|                 connectWaitLock.Release(); |  | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /// <inheritdoc/> |     /// <inheritdoc/> | ||||||
|     public virtual async ValueTask<OperResult> SendAsync(ISendMessage sendMessage, CancellationToken cancellationToken) |     public virtual ValueTask<OperResult> SendAsync(ISendMessage sendMessage, CancellationToken cancellationToken) | ||||||
|     { |     { | ||||||
|         try |         return SendAsync(this, sendMessage, cancellationToken); | ||||||
|         { |  | ||||||
|             var channelResult = GetChannel(); |  | ||||||
|             if (!channelResult.IsSuccess) return new OperResult<byte[]>(channelResult); |  | ||||||
|             WaitLock? waitLock = GetWaitLock(channelResult.Content); |  | ||||||
|  |  | ||||||
|  |         static async PooledValueTask<OperResult> SendAsync(DeviceBase @this, ISendMessage sendMessage, CancellationToken cancellationToken) | ||||||
|  |         { | ||||||
|             try |             try | ||||||
|             { |             { | ||||||
|                 await BeforeSendAsync(channelResult.Content, cancellationToken).ConfigureAwait(false); |                 var channelResult = @this.GetChannel(); | ||||||
|  |                 if (!channelResult.IsSuccess) return new OperResult<byte[]>(channelResult); | ||||||
|  |                 WaitLock? waitLock = @this.GetWaitLock(channelResult.Content); | ||||||
|  |  | ||||||
|                 await waitLock.WaitAsync(cancellationToken).ConfigureAwait(false); |                 try | ||||||
|                 channelResult.Content.SetDataHandlingAdapterLogger(Logger); |                 { | ||||||
|  |                     await @this.BeforeSendAsync(channelResult.Content, cancellationToken).ConfigureAwait(false); | ||||||
|  |  | ||||||
|  |                     await waitLock.WaitAsync(cancellationToken).ConfigureAwait(false); | ||||||
|  |                     channelResult.Content.SetDataHandlingAdapterLogger(@this.Logger); | ||||||
|  |  | ||||||
|  |  | ||||||
|                 await SendAsync(sendMessage, channelResult.Content, cancellationToken).ConfigureAwait(false); |                     await @this.SendAsync(sendMessage, channelResult.Content, cancellationToken).ConfigureAwait(false); | ||||||
|                 return OperResult.Success; |                     return OperResult.Success; | ||||||
|  |                 } | ||||||
|  |                 finally | ||||||
|  |                 { | ||||||
|  |                     waitLock.Release(); | ||||||
|  |                 } | ||||||
|             } |             } | ||||||
|             finally |             catch (Exception ex) | ||||||
|             { |             { | ||||||
|                 waitLock.Release(); |                 return new(ex); | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|         catch (Exception ex) |  | ||||||
|         { |  | ||||||
|             return new(ex); |  | ||||||
|         } |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /// <inheritdoc/> |     /// <inheritdoc/> | ||||||
| @@ -568,8 +582,8 @@ public abstract class DeviceBase : AsyncAndSyncDisposableObject, IDevice | |||||||
|                 try |                 try | ||||||
|                 { |                 { | ||||||
|  |  | ||||||
|                     var cts = reusableTimeout.GetTokenSource(timeout, cancellationToken, @this.Channel.ClosedToken); |                     var ctsToken = reusableTimeout.GetTokenSource(timeout, cancellationToken, @this.Channel.ClosedToken); | ||||||
|                     await waitData.WaitAsync(cts.Token).ConfigureAwait(false); |                     await waitData.WaitAsync(ctsToken).ConfigureAwait(false); | ||||||
|  |  | ||||||
|                 } |                 } | ||||||
|                 catch (OperationCanceledException) |                 catch (OperationCanceledException) | ||||||
| @@ -659,54 +673,59 @@ public abstract class DeviceBase : AsyncAndSyncDisposableObject, IDevice | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     /// <inheritdoc/> |     /// <inheritdoc/> | ||||||
|     public virtual async ValueTask<OperResult> WriteJTokenAsync(string address, JToken value, DataTypeEnum dataType, CancellationToken cancellationToken = default) |     public virtual ValueTask<OperResult> WriteJTokenAsync(string address, JToken value, DataTypeEnum dataType, CancellationToken cancellationToken = default) | ||||||
|     { |     { | ||||||
|         try |         return WriteJTokenAsync(this, address, value, dataType, cancellationToken); | ||||||
|  |  | ||||||
|  |         static async PooledValueTask<OperResult> WriteJTokenAsync(DeviceBase @this, string address, JToken value, DataTypeEnum dataType, CancellationToken cancellationToken) | ||||||
|         { |         { | ||||||
|             var bitConverter = ThingsGatewayBitConverter.GetTransByAddress(address); |             try | ||||||
|             if (value is JArray jArray) |  | ||||||
|             { |             { | ||||||
|                 return dataType switch |                 var bitConverter = @this.ThingsGatewayBitConverter.GetTransByAddress(address); | ||||||
|  |                 if (value is JArray jArray) | ||||||
|                 { |                 { | ||||||
|                     DataTypeEnum.String => await WriteAsync(address, jArray.ToObject<String[]>().AsMemory(), cancellationToken: cancellationToken).ConfigureAwait(false), |                     return dataType switch | ||||||
|                     DataTypeEnum.Boolean => await WriteAsync(address, jArray.ToObject<Boolean[]>().AsMemory(), cancellationToken).ConfigureAwait(false), |                     { | ||||||
|                     DataTypeEnum.Byte => await WriteAsync(address, jArray.ToObject<Byte[]>().AsMemory(), dataType, cancellationToken).ConfigureAwait(false), |                         DataTypeEnum.String => await @this.WriteAsync(address, jArray.ToObject<String[]>().AsMemory(), cancellationToken: cancellationToken).ConfigureAwait(false), | ||||||
|                     DataTypeEnum.Int16 => await WriteAsync(address, jArray.ToObject<Int16[]>().AsMemory(), cancellationToken: cancellationToken).ConfigureAwait(false), |                         DataTypeEnum.Boolean => await @this.WriteAsync(address, jArray.ToObject<Boolean[]>().AsMemory(), cancellationToken).ConfigureAwait(false), | ||||||
|                     DataTypeEnum.UInt16 => await WriteAsync(address, jArray.ToObject<UInt16[]>().AsMemory(), cancellationToken: cancellationToken).ConfigureAwait(false), |                         DataTypeEnum.Byte => await @this.WriteAsync(address, jArray.ToObject<Byte[]>().AsMemory(), dataType, cancellationToken).ConfigureAwait(false), | ||||||
|                     DataTypeEnum.Int32 => await WriteAsync(address, jArray.ToObject<Int32[]>().AsMemory(), cancellationToken: cancellationToken).ConfigureAwait(false), |                         DataTypeEnum.Int16 => await @this.WriteAsync(address, jArray.ToObject<Int16[]>().AsMemory(), cancellationToken: cancellationToken).ConfigureAwait(false), | ||||||
|                     DataTypeEnum.UInt32 => await WriteAsync(address, jArray.ToObject<UInt32[]>().AsMemory(), cancellationToken: cancellationToken).ConfigureAwait(false), |                         DataTypeEnum.UInt16 => await @this.WriteAsync(address, jArray.ToObject<UInt16[]>().AsMemory(), cancellationToken: cancellationToken).ConfigureAwait(false), | ||||||
|                     DataTypeEnum.Int64 => await WriteAsync(address, jArray.ToObject<Int64[]>().AsMemory(), cancellationToken: cancellationToken).ConfigureAwait(false), |                         DataTypeEnum.Int32 => await @this.WriteAsync(address, jArray.ToObject<Int32[]>().AsMemory(), cancellationToken: cancellationToken).ConfigureAwait(false), | ||||||
|                     DataTypeEnum.UInt64 => await WriteAsync(address, jArray.ToObject<UInt64[]>().AsMemory(), cancellationToken: cancellationToken).ConfigureAwait(false), |                         DataTypeEnum.UInt32 => await @this.WriteAsync(address, jArray.ToObject<UInt32[]>().AsMemory(), cancellationToken: cancellationToken).ConfigureAwait(false), | ||||||
|                     DataTypeEnum.Float => await WriteAsync(address, jArray.ToObject<Single[]>().AsMemory(), cancellationToken: cancellationToken).ConfigureAwait(false), |                         DataTypeEnum.Int64 => await @this.WriteAsync(address, jArray.ToObject<Int64[]>().AsMemory(), cancellationToken: cancellationToken).ConfigureAwait(false), | ||||||
|                     DataTypeEnum.Double => await WriteAsync(address, jArray.ToObject<Double[]>().AsMemory(), cancellationToken: cancellationToken).ConfigureAwait(false), |                         DataTypeEnum.UInt64 => await @this.WriteAsync(address, jArray.ToObject<UInt64[]>().AsMemory(), cancellationToken: cancellationToken).ConfigureAwait(false), | ||||||
|                     DataTypeEnum.Decimal => await WriteAsync(address, jArray.ToObject<Decimal[]>().AsMemory(), cancellationToken: cancellationToken).ConfigureAwait(false), |                         DataTypeEnum.Float => await @this.WriteAsync(address, jArray.ToObject<Single[]>().AsMemory(), cancellationToken: cancellationToken).ConfigureAwait(false), | ||||||
|                     _ => new OperResult(string.Format(AppResource.DataTypeNotSupported, dataType)), |                         DataTypeEnum.Double => await @this.WriteAsync(address, jArray.ToObject<Double[]>().AsMemory(), cancellationToken: cancellationToken).ConfigureAwait(false), | ||||||
|                 }; |                         DataTypeEnum.Decimal => await @this.WriteAsync(address, jArray.ToObject<Decimal[]>().AsMemory(), cancellationToken: cancellationToken).ConfigureAwait(false), | ||||||
|  |                         _ => new OperResult(string.Format(AppResource.DataTypeNotSupported, dataType)), | ||||||
|  |                     }; | ||||||
|  |                 } | ||||||
|  |                 else | ||||||
|  |                 { | ||||||
|  |                     return dataType switch | ||||||
|  |                     { | ||||||
|  |                         DataTypeEnum.String => await @this.WriteAsync(address, value.ToObject<String>(), bitConverter, cancellationToken).ConfigureAwait(false), | ||||||
|  |                         DataTypeEnum.Boolean => await @this.WriteAsync(address, value.ToObject<Boolean>(), bitConverter, cancellationToken).ConfigureAwait(false), | ||||||
|  |                         DataTypeEnum.Byte => await @this.WriteAsync(address, value.ToObject<Byte>(), bitConverter, cancellationToken).ConfigureAwait(false), | ||||||
|  |                         DataTypeEnum.Int16 => await @this.WriteAsync(address, value.ToObject<Int16>(), bitConverter, cancellationToken).ConfigureAwait(false), | ||||||
|  |                         DataTypeEnum.UInt16 => await @this.WriteAsync(address, value.ToObject<UInt16>(), bitConverter, cancellationToken).ConfigureAwait(false), | ||||||
|  |                         DataTypeEnum.Int32 => await @this.WriteAsync(address, value.ToObject<Int32>(), bitConverter, cancellationToken).ConfigureAwait(false), | ||||||
|  |                         DataTypeEnum.UInt32 => await @this.WriteAsync(address, value.ToObject<UInt32>(), bitConverter, cancellationToken).ConfigureAwait(false), | ||||||
|  |                         DataTypeEnum.Int64 => await @this.WriteAsync(address, value.ToObject<Int64>(), bitConverter, cancellationToken).ConfigureAwait(false), | ||||||
|  |                         DataTypeEnum.UInt64 => await @this.WriteAsync(address, value.ToObject<UInt64>(), bitConverter, cancellationToken).ConfigureAwait(false), | ||||||
|  |                         DataTypeEnum.Float => await @this.WriteAsync(address, value.ToObject<Single>(), bitConverter, cancellationToken).ConfigureAwait(false), | ||||||
|  |                         DataTypeEnum.Double => await @this.WriteAsync(address, value.ToObject<Double>(), bitConverter, cancellationToken).ConfigureAwait(false), | ||||||
|  |                         DataTypeEnum.Decimal => await @this.WriteAsync(address, value.ToObject<Decimal>(), bitConverter, cancellationToken).ConfigureAwait(false), | ||||||
|  |                         _ => new OperResult(string.Format(AppResource.DataTypeNotSupported, dataType)), | ||||||
|  |                     }; | ||||||
|  |                 } | ||||||
|             } |             } | ||||||
|             else |             catch (Exception ex) | ||||||
|             { |             { | ||||||
|                 return dataType switch |                 return new OperResult(ex); | ||||||
|                 { |  | ||||||
|                     DataTypeEnum.String => await WriteAsync(address, value.ToObject<String>(), bitConverter, cancellationToken).ConfigureAwait(false), |  | ||||||
|                     DataTypeEnum.Boolean => await WriteAsync(address, value.ToObject<Boolean>(), bitConverter, cancellationToken).ConfigureAwait(false), |  | ||||||
|                     DataTypeEnum.Byte => await WriteAsync(address, value.ToObject<Byte>(), bitConverter, cancellationToken).ConfigureAwait(false), |  | ||||||
|                     DataTypeEnum.Int16 => await WriteAsync(address, value.ToObject<Int16>(), bitConverter, cancellationToken).ConfigureAwait(false), |  | ||||||
|                     DataTypeEnum.UInt16 => await WriteAsync(address, value.ToObject<UInt16>(), bitConverter, cancellationToken).ConfigureAwait(false), |  | ||||||
|                     DataTypeEnum.Int32 => await WriteAsync(address, value.ToObject<Int32>(), bitConverter, cancellationToken).ConfigureAwait(false), |  | ||||||
|                     DataTypeEnum.UInt32 => await WriteAsync(address, value.ToObject<UInt32>(), bitConverter, cancellationToken).ConfigureAwait(false), |  | ||||||
|                     DataTypeEnum.Int64 => await WriteAsync(address, value.ToObject<Int64>(), bitConverter, cancellationToken).ConfigureAwait(false), |  | ||||||
|                     DataTypeEnum.UInt64 => await WriteAsync(address, value.ToObject<UInt64>(), bitConverter, cancellationToken).ConfigureAwait(false), |  | ||||||
|                     DataTypeEnum.Float => await WriteAsync(address, value.ToObject<Single>(), bitConverter, cancellationToken).ConfigureAwait(false), |  | ||||||
|                     DataTypeEnum.Double => await WriteAsync(address, value.ToObject<Double>(), bitConverter, cancellationToken).ConfigureAwait(false), |  | ||||||
|                     DataTypeEnum.Decimal => await WriteAsync(address, value.ToObject<Decimal>(), bitConverter, cancellationToken).ConfigureAwait(false), |  | ||||||
|                     _ => new OperResult(string.Format(AppResource.DataTypeNotSupported, dataType)), |  | ||||||
|                 }; |  | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|         catch (Exception ex) |  | ||||||
|         { |  | ||||||
|             return new OperResult(ex); |  | ||||||
|         } |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     #endregion 动态类型读写 |     #endregion 动态类型读写 | ||||||
|   | |||||||
| @@ -8,6 +8,8 @@ | |||||||
| //  QQ群:605534569 | //  QQ群:605534569 | ||||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||||
|  |  | ||||||
|  | using PooledAwait; | ||||||
|  |  | ||||||
| using TouchSocket.Core; | using TouchSocket.Core; | ||||||
|  |  | ||||||
| namespace ThingsGateway.Gateway.Application; | namespace ThingsGateway.Gateway.Application; | ||||||
| @@ -28,22 +30,26 @@ public class AsyncReadWriteLock : IAsyncDisposable | |||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// 获取读锁,支持多个线程并发读取,但写入时会阻止所有读取。 |     /// 获取读锁,支持多个线程并发读取,但写入时会阻止所有读取。 | ||||||
|     /// </summary> |     /// </summary> | ||||||
|     public async ValueTask<CancellationToken> ReaderLockAsync(CancellationToken cancellationToken) |     public ValueTask<CancellationToken> ReaderLockAsync(CancellationToken cancellationToken) | ||||||
|     { |     { | ||||||
|  |         return ReaderLockAsync(this, cancellationToken); | ||||||
|  |  | ||||||
|         if (Interlocked.Read(ref _writerCount) > 0) |         static async PooledValueTask<CancellationToken> ReaderLockAsync(AsyncReadWriteLock @this, CancellationToken cancellationToken) | ||||||
|         { |         { | ||||||
|             Interlocked.Increment(ref _readerCount); |             if (Interlocked.Read(ref @this._writerCount) > 0) | ||||||
|  |             { | ||||||
|  |                 Interlocked.Increment(ref @this._readerCount); | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|             // 第一个读者需要获取写入锁,防止写操作 |                 // 第一个读者需要获取写入锁,防止写操作 | ||||||
|             await _readerLock.WaitOneAsync(cancellationToken).ConfigureAwait(false); |                 await @this._readerLock.WaitOneAsync(cancellationToken).ConfigureAwait(false); | ||||||
|  |  | ||||||
|             Interlocked.Decrement(ref _readerCount); |                 Interlocked.Decrement(ref @this._readerCount); | ||||||
|  |  | ||||||
|  |             } | ||||||
|  |             return @this._cancellationTokenSource.Token; | ||||||
|         } |         } | ||||||
|         return _cancellationTokenSource.Token; |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     public bool WriteWaited => _writerCount > 0; |     public bool WriteWaited => _writerCount > 0; | ||||||
| @@ -51,21 +57,25 @@ public class AsyncReadWriteLock : IAsyncDisposable | |||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// 获取写锁,阻止所有读取。 |     /// 获取写锁,阻止所有读取。 | ||||||
|     /// </summary> |     /// </summary> | ||||||
|     public async ValueTask<IDisposable> WriterLockAsync(CancellationToken cancellationToken) |     public ValueTask<IDisposable> WriterLockAsync(CancellationToken cancellationToken) | ||||||
|     { |     { | ||||||
|  |         return WriterLockAsync(this); | ||||||
|  |  | ||||||
|         if (Interlocked.Increment(ref _writerCount) == 1) |         static async PooledValueTask<IDisposable> WriterLockAsync(AsyncReadWriteLock @this) | ||||||
|         { |         { | ||||||
|             if (_writePriority) |             if (Interlocked.Increment(ref @this._writerCount) == 1) | ||||||
|             { |             { | ||||||
|                 var cancellationTokenSource = _cancellationTokenSource; |                 if (@this._writePriority) | ||||||
|                 _cancellationTokenSource = new(); |                 { | ||||||
|                 await cancellationTokenSource.SafeCancelAsync().ConfigureAwait(false); // 取消读取 |                     var cancellationTokenSource = @this._cancellationTokenSource; | ||||||
|                 cancellationTokenSource.SafeDispose(); |                     @this._cancellationTokenSource = new(); | ||||||
|  |                     await cancellationTokenSource.SafeCancelAsync().ConfigureAwait(false); // 取消读取 | ||||||
|  |                     cancellationTokenSource.SafeDispose(); | ||||||
|  |                 } | ||||||
|             } |             } | ||||||
|         } |  | ||||||
|  |  | ||||||
|         return new Writer(this); |             return new Writer(@this); | ||||||
|  |         } | ||||||
|     } |     } | ||||||
|     private object lockObject = new(); |     private object lockObject = new(); | ||||||
|     private void ReleaseWriter() |     private void ReleaseWriter() | ||||||
|   | |||||||
| @@ -1,146 +0,0 @@ | |||||||
| ////------------------------------------------------------------------------------ |  | ||||||
| ////  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 |  | ||||||
| ////  此代码版权(除特别声明外的代码)归作者本人Diego所有 |  | ||||||
| ////  源代码使用协议遵循本仓库的开源协议及附加协议 |  | ||||||
| ////  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway |  | ||||||
| ////  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway |  | ||||||
| ////  使用文档:https://thingsgateway.cn/ |  | ||||||
| ////  QQ群:605534569 |  | ||||||
| ////------------------------------------------------------------------------------ |  | ||||||
|  |  | ||||||
| //using Microsoft.Extensions.Logging; |  | ||||||
|  |  | ||||||
| //using ThingsGateway.NewLife; |  | ||||||
|  |  | ||||||
| //using TouchSocket.Core; |  | ||||||
|  |  | ||||||
| //namespace ThingsGateway.Gateway.Application; |  | ||||||
|  |  | ||||||
| //[ThingsGateway.DependencyInjection.SuppressSniffer] |  | ||||||
| //public class DoTask |  | ||||||
| //{ |  | ||||||
| //    /// <summary> |  | ||||||
| //    /// 取消令牌 |  | ||||||
| //    /// </summary> |  | ||||||
| //    private CancellationTokenSource? _cancelTokenSource; |  | ||||||
| //    private object? _state; |  | ||||||
|  |  | ||||||
| //    public DoTask(Func<object?, CancellationToken, Task> doWork, ILog logger, object? state = null, string taskName = null) |  | ||||||
| //    { |  | ||||||
| //        DoWork = doWork; Logger = logger; TaskName = taskName; _state = state; |  | ||||||
| //    } |  | ||||||
|  |  | ||||||
| //    /// <summary> |  | ||||||
| //    /// 执行任务方法 |  | ||||||
| //    /// </summary> |  | ||||||
| //    public Func<object?, CancellationToken, Task> DoWork { get; } |  | ||||||
| //    private ILog Logger { get; } |  | ||||||
| //    private Task PrivateTask { get; set; } |  | ||||||
| //    private string TaskName { get; } |  | ||||||
|  |  | ||||||
| //    /// <summary> |  | ||||||
| //    /// 开始 |  | ||||||
| //    /// </summary> |  | ||||||
| //    /// <param name="cancellationToken">调度取消令牌</param> |  | ||||||
| //    public void Start(CancellationToken cancellationToken) |  | ||||||
| //    { |  | ||||||
| //        try |  | ||||||
| //        { |  | ||||||
| //            WaitLock.Wait(cancellationToken); |  | ||||||
|  |  | ||||||
| //            if (cancellationToken.CanBeCanceled) |  | ||||||
| //            { |  | ||||||
| //                _cancelTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); |  | ||||||
| //            } |  | ||||||
| //            else |  | ||||||
| //            { |  | ||||||
| //                _cancelTokenSource = new CancellationTokenSource(); |  | ||||||
| //            } |  | ||||||
|  |  | ||||||
| //            // 异步执行 |  | ||||||
| //            PrivateTask = Do(); |  | ||||||
| //        } |  | ||||||
| //        finally |  | ||||||
| //        { |  | ||||||
| //            WaitLock.Release(); |  | ||||||
| //        } |  | ||||||
| //    } |  | ||||||
|  |  | ||||||
| //    private async Task Do() |  | ||||||
| //    { |  | ||||||
| //        await Task.Yield(); |  | ||||||
| //        while (!_cancelTokenSource.IsCancellationRequested) |  | ||||||
| //        { |  | ||||||
| //            try |  | ||||||
| //            { |  | ||||||
| //                if (_cancelTokenSource.IsCancellationRequested) |  | ||||||
| //                    return; |  | ||||||
| //                await DoWork(_state, _cancelTokenSource.Token).ConfigureAwait(false); |  | ||||||
| //            } |  | ||||||
| //            catch (OperationCanceledException) |  | ||||||
| //            { |  | ||||||
| //            } |  | ||||||
| //            catch (ObjectDisposedException) |  | ||||||
| //            { |  | ||||||
| //            } |  | ||||||
| //            catch (Exception ex) |  | ||||||
| //            { |  | ||||||
| //                Logger?.LogWarning(ex, "DoWork"); |  | ||||||
| //            } |  | ||||||
| //        } |  | ||||||
| //    } |  | ||||||
|  |  | ||||||
| //    private WaitLock WaitLock = new(); |  | ||||||
| //    /// <summary> |  | ||||||
| //    /// 停止操作 |  | ||||||
| //    /// </summary> |  | ||||||
| //    public async Task StopAsync(TimeSpan? waitTime = null) |  | ||||||
| //    { |  | ||||||
| //        try |  | ||||||
| //        { |  | ||||||
| //            await WaitLock.WaitAsync().ConfigureAwait(false); |  | ||||||
|  |  | ||||||
| //            try |  | ||||||
| //            { |  | ||||||
| //                _cancelTokenSource?.Cancel(); |  | ||||||
| //                _cancelTokenSource?.Dispose(); |  | ||||||
| //            } |  | ||||||
| //            catch (Exception ex) |  | ||||||
| //            { |  | ||||||
| //                Logger?.LogWarning(ex, "Cancel error"); |  | ||||||
| //            } |  | ||||||
|  |  | ||||||
| //            if (PrivateTask != null) |  | ||||||
| //            { |  | ||||||
| //                try |  | ||||||
| //                { |  | ||||||
| //                    if (TaskName != null) |  | ||||||
| //                        Logger?.LogInformation($"{TaskName} Stoping"); |  | ||||||
| //                    if (waitTime != null) |  | ||||||
| //                        await PrivateTask.WaitAsync(waitTime.Value).ConfigureAwait(false); |  | ||||||
| //                    if (TaskName != null) |  | ||||||
| //                        Logger?.LogInformation($"{TaskName} Stoped"); |  | ||||||
| //                } |  | ||||||
| //                catch (ObjectDisposedException) |  | ||||||
| //                { |  | ||||||
| //                } |  | ||||||
| //                catch (TimeoutException) |  | ||||||
| //                { |  | ||||||
| //                    if (TaskName != null) |  | ||||||
| //                        Logger?.LogWarning($"{TaskName} Stop timeout, exiting wait block"); |  | ||||||
| //                } |  | ||||||
| //                catch (Exception ex) |  | ||||||
| //                { |  | ||||||
| //                    if (TaskName != null) |  | ||||||
| //                        Logger?.LogWarning(ex, $"{TaskName} Stop error"); |  | ||||||
| //                } |  | ||||||
| //                PrivateTask = null; |  | ||||||
|  |  | ||||||
| //            } |  | ||||||
| //        } |  | ||||||
| //        finally |  | ||||||
| //        { |  | ||||||
| //            WaitLock.Release(); |  | ||||||
| //        } |  | ||||||
| //    } |  | ||||||
| //} |  | ||||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -20,6 +20,8 @@ using ThingsGateway.Common.Extension; | |||||||
| using ThingsGateway.Extension.Generic; | using ThingsGateway.Extension.Generic; | ||||||
| #if !Management | #if !Management | ||||||
| using ThingsGateway.Gateway.Application.Extensions; | using ThingsGateway.Gateway.Application.Extensions; | ||||||
|  | using ThingsGateway.NewLife; | ||||||
|  |  | ||||||
| #endif | #endif | ||||||
| using ThingsGateway.NewLife.Json.Extension; | using ThingsGateway.NewLife.Json.Extension; | ||||||
| using ThingsGateway.NewLife.Threading; | using ThingsGateway.NewLife.Threading; | ||||||
| @@ -292,69 +294,75 @@ public abstract partial class CollectBase : DriverBase | |||||||
|  |  | ||||||
|  |  | ||||||
|     #region 执行方法 |     #region 执行方法 | ||||||
|     async ValueTask ReadVariableMed(object? state, CancellationToken cancellationToken) |     ValueTask ReadVariableMed(object? state, CancellationToken cancellationToken) | ||||||
|     { |     { | ||||||
|         if (state is not VariableMethod readVariableMethods) return; |         if (state is not VariableMethod readVariableMethods) | ||||||
|  |             return ValueTask.CompletedTask; | ||||||
|         if (Pause) |         if (Pause) | ||||||
|             return; |             return ValueTask.CompletedTask; | ||||||
|         if (cancellationToken.IsCancellationRequested) |         if (cancellationToken.IsCancellationRequested) | ||||||
|             return; |             return ValueTask.CompletedTask; | ||||||
|  |         return ReadVariableMed(this, readVariableMethods, cancellationToken); | ||||||
|  |  | ||||||
|         var readErrorCount = 0; |         static async PooledValueTask ReadVariableMed(CollectBase @this, VariableMethod readVariableMethods, CancellationToken cancellationToken) | ||||||
|  |  | ||||||
|         //if (LogMessage?.LogLevel <= TouchSocket.Core.LogLevel.Trace) |  | ||||||
|         //    LogMessage?.Trace(string.Format("{0} - Executing method [{1}]", DeviceName, readVariableMethods.MethodInfo.Name)); |  | ||||||
|         var readResult = await InvokeMethodAsync(readVariableMethods, cancellationToken: cancellationToken).ConfigureAwait(false); |  | ||||||
|  |  | ||||||
|         // 方法调用失败时重试一定次数 |  | ||||||
|         while (!readResult.IsSuccess && readErrorCount < CollectProperties.RetryCount) |  | ||||||
|         { |         { | ||||||
|             if (Pause) |  | ||||||
|                 return; |  | ||||||
|             if (cancellationToken.IsCancellationRequested) |  | ||||||
|                 return; |  | ||||||
|  |  | ||||||
|             readErrorCount++; |             var readErrorCount = 0; | ||||||
|             if (LogMessage?.LogLevel <= TouchSocket.Core.LogLevel.Trace) |  | ||||||
|                 LogMessage?.Trace(string.Format("{0} - Execute method [{1}] - failed - {2}", DeviceName, readVariableMethods.MethodInfo.Name, readResult.ErrorMessage)); |  | ||||||
|  |  | ||||||
|             //if (LogMessage?.LogLevel <= TouchSocket.Core.LogLevel.Trace) |             //if (LogMessage?.LogLevel <= TouchSocket.Core.LogLevel.Trace) | ||||||
|             //    LogMessage?.Trace(string.Format("{0} - Executing method [{1}]", DeviceName, readVariableMethods.MethodInfo.Name)); |             //    LogMessage?.Trace(string.Format("{0} - Executing method [{1}]", DeviceName, readVariableMethods.MethodInfo.Name)); | ||||||
|             readResult = await InvokeMethodAsync(readVariableMethods, cancellationToken: cancellationToken).ConfigureAwait(false); |             var readResult = await @this.InvokeMethodAsync(readVariableMethods, cancellationToken: cancellationToken).ConfigureAwait(false); | ||||||
|         } |  | ||||||
|  |  | ||||||
|         if (readResult.IsSuccess) |             // 方法调用失败时重试一定次数 | ||||||
|         { |             while (!readResult.IsSuccess && readErrorCount < @this.CollectProperties.RetryCount) | ||||||
|             // 方法调用成功时记录日志并增加成功计数器 |  | ||||||
|             if (LogMessage?.LogLevel <= TouchSocket.Core.LogLevel.Trace) |  | ||||||
|                 LogMessage?.Trace(string.Format("{0} - Execute method [{1}] - Succeeded {2}", DeviceName, readVariableMethods.MethodInfo.Name, readResult.Content?.ToSystemTextJsonString())); |  | ||||||
|             CurrentDevice.SetDeviceStatus(TimerX.Now, null); |  | ||||||
|         } |  | ||||||
|         else |  | ||||||
|         { |  | ||||||
|             if (cancellationToken.IsCancellationRequested) |  | ||||||
|                 return; |  | ||||||
|  |  | ||||||
|             // 方法调用失败时记录日志并增加失败计数器,更新错误信息 |  | ||||||
|             if (readVariableMethods.LastErrorMessage != readResult.ErrorMessage) |  | ||||||
|             { |             { | ||||||
|                 if (!cancellationToken.IsCancellationRequested) |                 if (@this.Pause) | ||||||
|                     LogMessage?.LogWarning(readResult.Exception, string.Format(AppResource.MethodFail, DeviceName, readVariableMethods.MethodInfo.Name, readResult.ErrorMessage)); |                     return; | ||||||
|  |                 if (cancellationToken.IsCancellationRequested) | ||||||
|  |                     return; | ||||||
|  |  | ||||||
|  |                 readErrorCount++; | ||||||
|  |                 if (@this.LogMessage?.LogLevel <= TouchSocket.Core.LogLevel.Trace) | ||||||
|  |                     @this.LogMessage?.Trace(string.Format("{0} - Execute method [{1}] - failed - {2}", @this.DeviceName, readVariableMethods.MethodInfo.Name, readResult.ErrorMessage)); | ||||||
|  |  | ||||||
|  |                 //if (LogMessage?.LogLevel <= TouchSocket.Core.LogLevel.Trace) | ||||||
|  |                 //    LogMessage?.Trace(string.Format("{0} - Executing method [{1}]", DeviceName, readVariableMethods.MethodInfo.Name)); | ||||||
|  |                 readResult = await @this.InvokeMethodAsync(readVariableMethods, cancellationToken: cancellationToken).ConfigureAwait(false); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             if (readResult.IsSuccess) | ||||||
|  |             { | ||||||
|  |                 // 方法调用成功时记录日志并增加成功计数器 | ||||||
|  |                 if (@this.LogMessage?.LogLevel <= TouchSocket.Core.LogLevel.Trace) | ||||||
|  |                     @this.LogMessage?.Trace(string.Format("{0} - Execute method [{1}] - Succeeded {2}", @this.DeviceName, readVariableMethods.MethodInfo.Name, readResult.Content?.ToSystemTextJsonString())); | ||||||
|  |                 @this.CurrentDevice.SetDeviceStatus(TimerX.Now, null); | ||||||
|             } |             } | ||||||
|             else |             else | ||||||
|             { |             { | ||||||
|                 if (!cancellationToken.IsCancellationRequested) |                 if (cancellationToken.IsCancellationRequested) | ||||||
|  |                     return; | ||||||
|  |  | ||||||
|  |                 // 方法调用失败时记录日志并增加失败计数器,更新错误信息 | ||||||
|  |                 if (readVariableMethods.LastErrorMessage != readResult.ErrorMessage) | ||||||
|                 { |                 { | ||||||
|                     if (LogMessage?.LogLevel <= TouchSocket.Core.LogLevel.Trace) |                     if (!cancellationToken.IsCancellationRequested) | ||||||
|                         LogMessage?.Trace(string.Format("{0} - Execute method [{1}] - failed - {2}", DeviceName, readVariableMethods.MethodInfo.Name, readResult.ErrorMessage)); |                         @this.LogMessage?.LogWarning(readResult.Exception, string.Format(AppResource.MethodFail, @this.DeviceName, readVariableMethods.MethodInfo.Name, readResult.ErrorMessage)); | ||||||
|                 } |                 } | ||||||
|  |                 else | ||||||
|  |                 { | ||||||
|  |                     if (!cancellationToken.IsCancellationRequested) | ||||||
|  |                     { | ||||||
|  |                         if (@this.LogMessage?.LogLevel <= TouchSocket.Core.LogLevel.Trace) | ||||||
|  |                             @this.LogMessage?.Trace(string.Format("{0} - Execute method [{1}] - failed - {2}", @this.DeviceName, readVariableMethods.MethodInfo.Name, readResult.ErrorMessage)); | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 readVariableMethods.LastErrorMessage = readResult.ErrorMessage; | ||||||
|  |                 @this.CurrentDevice.SetDeviceStatus(TimerX.Now, null); | ||||||
|             } |             } | ||||||
|  |  | ||||||
|             readVariableMethods.LastErrorMessage = readResult.ErrorMessage; |             return; | ||||||
|             CurrentDevice.SetDeviceStatus(TimerX.Now, null); |  | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         return; |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     #endregion |     #endregion | ||||||
| @@ -674,9 +682,9 @@ public abstract partial class CollectBase : DriverBase | |||||||
|     #endregion |     #endregion | ||||||
|  |  | ||||||
|  |  | ||||||
|     protected virtual Task TestOnline(object? state, CancellationToken cancellationToken) |     protected virtual ValueTask TestOnline(object? state, CancellationToken cancellationToken) | ||||||
|     { |     { | ||||||
|         return Task.CompletedTask; |         return ValueTask.CompletedTask; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     protected void ScriptVariableRun(object? state, CancellationToken cancellationToken) |     protected void ScriptVariableRun(object? state, CancellationToken cancellationToken) | ||||||
| @@ -727,38 +735,43 @@ public abstract partial class CollectBase : DriverBase | |||||||
|     { |     { | ||||||
|         throw new NotImplementedException(); |         throw new NotImplementedException(); | ||||||
|     } |     } | ||||||
|     protected async Task Check(Dictionary<VariableRuntime, JToken> writeInfoLists, NonBlockingDictionary<string, OperResult> operResults, CancellationToken cancellationToken) |     protected Task Check(Dictionary<VariableRuntime, JToken> writeInfoLists, NonBlockingDictionary<string, OperResult> operResults, CancellationToken cancellationToken) | ||||||
|     { |     { | ||||||
|         if (VariableSourceReadsEnable) |         return Check(this, writeInfoLists, operResults, cancellationToken); | ||||||
|  |  | ||||||
|  |         static async PooledTask Check(CollectBase @this, Dictionary<VariableRuntime, JToken> writeInfoLists, NonBlockingDictionary<string, OperResult> operResults, CancellationToken cancellationToken) | ||||||
|         { |         { | ||||||
|             // 如果成功,每个变量都读取一次最新值,再次比较写入值 |             if (@this.VariableSourceReadsEnable) | ||||||
|             var successfulWriteNames = operResults.Where(a => a.Value.IsSuccess).Select(a => a.Key).ToHashSet(); |  | ||||||
|  |  | ||||||
|             var groups = writeInfoLists.Select(a => a.Key).Where(a => a.RpcWriteCheck && a.ProtectType != ProtectTypeEnum.WriteOnly && successfulWriteNames.Contains(a.Name) && a.VariableSource != null).GroupBy(a => a.VariableSource as VariableSourceRead).Where(a => a.Key != null).ToArray(); |  | ||||||
|  |  | ||||||
|             await groups.ParallelForEachAsync(async (varRead, token) => |  | ||||||
|             { |             { | ||||||
|                 var result = await ReadSourceAsync(varRead.Key, token).ConfigureAwait(false); |                 // 如果成功,每个变量都读取一次最新值,再次比较写入值 | ||||||
|                 if (result.IsSuccess) |                 var successfulWriteNames = operResults.Where(a => a.Value.IsSuccess).Select(a => a.Key).ToHashSet(); | ||||||
|  |  | ||||||
|  |                 var groups = writeInfoLists.Select(a => a.Key).Where(a => a.RpcWriteCheck && a.ProtectType != ProtectTypeEnum.WriteOnly && successfulWriteNames.Contains(a.Name) && a.VariableSource != null).GroupBy(a => a.VariableSource as VariableSourceRead).Where(a => a.Key != null).ToArray(); | ||||||
|  |  | ||||||
|  |                 await groups.ParallelForEachAsync(async (varRead, token) => | ||||||
|                 { |                 { | ||||||
|                     foreach (var item in varRead) |                     var result = await @this.ReadSourceAsync(varRead.Key, token).ConfigureAwait(false); | ||||||
|  |                     if (result.IsSuccess) | ||||||
|                     { |                     { | ||||||
|                         if (!item.Value.Equals(writeInfoLists[item].ToObject(item.Value?.GetType()))) |                         foreach (var item in varRead) | ||||||
|                         { |                         { | ||||||
|                             // 如果写入值与读取值不同,则更新操作结果为失败 |                             if (!item.Value.Equals(writeInfoLists[item].ToObject(item.Value?.GetType()))) | ||||||
|                             operResults[item.Name] = new OperResult($"The write value is inconsistent with the read value,  Write value: {writeInfoLists[item].ToObject(item.Value?.GetType())}, read value: {item.Value}"); |                             { | ||||||
|  |                                 // 如果写入值与读取值不同,则更新操作结果为失败 | ||||||
|  |                                 operResults[item.Name] = new OperResult($"The write value is inconsistent with the read value,  Write value: {writeInfoLists[item].ToObject(item.Value?.GetType())}, read value: {item.Value}"); | ||||||
|  |                             } | ||||||
|                         } |                         } | ||||||
|                     } |                     } | ||||||
|                 } |                     else | ||||||
|                 else |  | ||||||
|                 { |  | ||||||
|                     foreach (var item in varRead) |  | ||||||
|                     { |                     { | ||||||
|                         // 如果写入值与读取值不同,则更新操作结果为失败 |                         foreach (var item in varRead) | ||||||
|                         operResults[item.Name] = new OperResult($"Reading and rechecking resulted in an error: {result.ErrorMessage}", result.Exception); |                         { | ||||||
|  |                             // 如果写入值与读取值不同,则更新操作结果为失败 | ||||||
|  |                             operResults[item.Name] = new OperResult($"Reading and rechecking resulted in an error: {result.ErrorMessage}", result.Exception); | ||||||
|  |                         } | ||||||
|                     } |                     } | ||||||
|                 } |                 }, cancellationToken).ConfigureAwait(false); | ||||||
|             }, cancellationToken).ConfigureAwait(false); |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -770,66 +783,71 @@ public abstract partial class CollectBase : DriverBase | |||||||
|     /// <param name="writeInfoLists">要写入的变量及其对应的数据</param> |     /// <param name="writeInfoLists">要写入的变量及其对应的数据</param> | ||||||
|     /// <param name="cancellationToken">取消操作的通知</param> |     /// <param name="cancellationToken">取消操作的通知</param> | ||||||
|     /// <returns>写入操作的结果字典</returns> |     /// <returns>写入操作的结果字典</returns> | ||||||
|     public async ValueTask<Dictionary<string, Dictionary<string, IOperResult>>> InvokeMethodAsync(Dictionary<VariableRuntime, JToken> writeInfoLists, CancellationToken cancellationToken) |     public ValueTask<Dictionary<string, Dictionary<string, IOperResult>>> InvokeMethodAsync(Dictionary<VariableRuntime, JToken> writeInfoLists, CancellationToken cancellationToken) | ||||||
|     { |     { | ||||||
|         // 初始化结果字典 |         return InvokeMethodAsync(this, writeInfoLists, cancellationToken); | ||||||
|         Dictionary<string, OperResult<object>> results = new Dictionary<string, OperResult<object>>(); |  | ||||||
|  |  | ||||||
|         // 遍历写入信息列表 |         static async PooledValueTask<Dictionary<string, Dictionary<string, IOperResult>>> InvokeMethodAsync(CollectBase @this, Dictionary<VariableRuntime, JToken> writeInfoLists, CancellationToken cancellationToken) | ||||||
|         foreach (var (deviceVariable, jToken) in writeInfoLists) |  | ||||||
|         { |         { | ||||||
|             // 检查是否有写入表达式 |             // 初始化结果字典 | ||||||
|             if (!string.IsNullOrEmpty(deviceVariable.WriteExpressions)) |             Dictionary<string, OperResult<object>> results = new Dictionary<string, OperResult<object>>(); | ||||||
|  |  | ||||||
|  |             // 遍历写入信息列表 | ||||||
|  |             foreach (var (deviceVariable, jToken) in writeInfoLists) | ||||||
|  |             { | ||||||
|  |                 // 检查是否有写入表达式 | ||||||
|  |                 if (!string.IsNullOrEmpty(deviceVariable.WriteExpressions)) | ||||||
|  |                 { | ||||||
|  |                     // 提取原始数据 | ||||||
|  |                     object rawdata = jToken.GetObjectFromJToken(); | ||||||
|  |                     try | ||||||
|  |                     { | ||||||
|  |                         // 根据写入表达式转换数据 | ||||||
|  |                         object data = deviceVariable.WriteExpressions.GetExpressionsResult(rawdata, @this.LogMessage); | ||||||
|  |                         // 将转换后的数据重新赋值给写入信息列表 | ||||||
|  |                         writeInfoLists[deviceVariable] = JTokenUtil.GetJTokenFromObj(data); | ||||||
|  |                     } | ||||||
|  |                     catch (Exception ex) | ||||||
|  |                     { | ||||||
|  |                         // 如果转换失败,则记录错误信息 | ||||||
|  |                         results.Add(deviceVariable.Name, new OperResult<object>(string.Format(AppResource.WriteExpressionsError, deviceVariable.Name, deviceVariable.WriteExpressions, ex.Message), ex)); | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             NonBlockingDictionary<string, OperResult<object>> operResults = new(); | ||||||
|  |  | ||||||
|  |  | ||||||
|  |             using var writeLock = await @this.ReadWriteLock.WriterLockAsync(cancellationToken).ConfigureAwait(false); | ||||||
|  |             var list = writeInfoLists | ||||||
|  |             .Where(a => !results.Any(b => b.Key == a.Key.Name)) | ||||||
|  |             .ToArray(); | ||||||
|  |             // 使用并发方式遍历写入信息列表,并进行异步写入操作 | ||||||
|  |             await list.ParallelForEachAsync(async (writeInfo, cancellationToken) => | ||||||
|             { |             { | ||||||
|                 // 提取原始数据 |  | ||||||
|                 object rawdata = jToken.GetObjectFromJToken(); |  | ||||||
|                 try |                 try | ||||||
|                 { |                 { | ||||||
|                     // 根据写入表达式转换数据 |                     // 调用协议的写入方法,将写入信息中的数据写入到对应的寄存器地址,并获取操作结果 | ||||||
|                     object data = deviceVariable.WriteExpressions.GetExpressionsResult(rawdata, LogMessage); |                     var result = await @this.InvokeMethodAsync(writeInfo.Key.VariableMethod, writeInfo.Value?.ToString(), false, cancellationToken).ConfigureAwait(false); | ||||||
|                     // 将转换后的数据重新赋值给写入信息列表 |  | ||||||
|                     writeInfoLists[deviceVariable] = JTokenUtil.GetJTokenFromObj(data); |                     // 将操作结果添加到结果字典中,使用变量名称作为键 | ||||||
|  |                     operResults.TryAdd(writeInfo.Key.Name, result); | ||||||
|                 } |                 } | ||||||
|                 catch (Exception ex) |                 catch (Exception ex) | ||||||
|                 { |                 { | ||||||
|                     // 如果转换失败,则记录错误信息 |                     operResults.TryAdd(writeInfo.Key.Name, new(ex)); | ||||||
|                     results.Add(deviceVariable.Name, new OperResult<object>(string.Format(AppResource.WriteExpressionsError, deviceVariable.Name, deviceVariable.WriteExpressions, ex.Message), ex)); |  | ||||||
|                 } |                 } | ||||||
|             } |             }, @this.CollectProperties.MaxConcurrentCount, cancellationToken).ConfigureAwait(false); | ||||||
|         } |  | ||||||
|  |  | ||||||
|         NonBlockingDictionary<string, OperResult<object>> operResults = new(); |             // 将转换失败的变量和写入成功的变量的操作结果合并到结果字典中 | ||||||
|  |             return new Dictionary<string, Dictionary<string, IOperResult>>() | ||||||
|  |  | ||||||
|         using var writeLock = await ReadWriteLock.WriterLockAsync(cancellationToken).ConfigureAwait(false); |  | ||||||
|         var list = writeInfoLists |  | ||||||
|         .Where(a => !results.Any(b => b.Key == a.Key.Name)) |  | ||||||
|         .ToArray(); |  | ||||||
|         // 使用并发方式遍历写入信息列表,并进行异步写入操作 |  | ||||||
|         await list.ParallelForEachAsync(async (writeInfo, cancellationToken) => |  | ||||||
|         { |  | ||||||
|             try |  | ||||||
|             { |  | ||||||
|                 // 调用协议的写入方法,将写入信息中的数据写入到对应的寄存器地址,并获取操作结果 |  | ||||||
|                 var result = await InvokeMethodAsync(writeInfo.Key.VariableMethod, writeInfo.Value?.ToString(), false, cancellationToken).ConfigureAwait(false); |  | ||||||
|  |  | ||||||
|                 // 将操作结果添加到结果字典中,使用变量名称作为键 |  | ||||||
|                 operResults.TryAdd(writeInfo.Key.Name, result); |  | ||||||
|             } |  | ||||||
|             catch (Exception ex) |  | ||||||
|             { |  | ||||||
|                 operResults.TryAdd(writeInfo.Key.Name, new(ex)); |  | ||||||
|             } |  | ||||||
|         }, CollectProperties.MaxConcurrentCount, cancellationToken).ConfigureAwait(false); |  | ||||||
|  |  | ||||||
|         // 将转换失败的变量和写入成功的变量的操作结果合并到结果字典中 |  | ||||||
|         return new Dictionary<string, Dictionary<string, IOperResult>>() |  | ||||||
|         { |         { | ||||||
|             { |             { | ||||||
|              DeviceName , |              @this.DeviceName , | ||||||
|              results.Concat(operResults).ToDictionary(a => a.Key, a => (IOperResult)a.Value) |              results.Concat(operResults).ToDictionary(a => a.Key, a => (IOperResult)a.Value) | ||||||
|             } |             } | ||||||
|         }; |         }; | ||||||
|  |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /// <summary> |     /// <summary> | ||||||
| @@ -838,58 +856,63 @@ public abstract partial class CollectBase : DriverBase | |||||||
|     /// <param name="writeInfoLists">要写入的变量及其对应的数据</param> |     /// <param name="writeInfoLists">要写入的变量及其对应的数据</param> | ||||||
|     /// <param name="cancellationToken">取消操作的通知</param> |     /// <param name="cancellationToken">取消操作的通知</param> | ||||||
|     /// <returns>写入操作的结果字典</returns> |     /// <returns>写入操作的结果字典</returns> | ||||||
|     public async ValueTask<Dictionary<string, Dictionary<string, IOperResult>>> InVokeWriteAsync(Dictionary<VariableRuntime, JToken> writeInfoLists, CancellationToken cancellationToken) |     public ValueTask<Dictionary<string, Dictionary<string, IOperResult>>> InVokeWriteAsync(Dictionary<VariableRuntime, JToken> writeInfoLists, CancellationToken cancellationToken) | ||||||
|     { |     { | ||||||
|         // 初始化结果字典 |         return InVokeWriteAsync(this, writeInfoLists, cancellationToken); | ||||||
|         Dictionary<string, OperResult> results = new Dictionary<string, OperResult>(); |  | ||||||
|  |  | ||||||
|         // 遍历写入信息列表 |         static async PooledValueTask<Dictionary<string, Dictionary<string, IOperResult>>> InVokeWriteAsync(CollectBase @this, Dictionary<VariableRuntime, JToken> writeInfoLists, CancellationToken cancellationToken) | ||||||
|         foreach (var (deviceVariable, jToken) in writeInfoLists) |  | ||||||
|         { |         { | ||||||
|             // 检查是否有写入表达式 |             // 初始化结果字典 | ||||||
|             if (!string.IsNullOrEmpty(deviceVariable.WriteExpressions)) |             Dictionary<string, OperResult> results = new Dictionary<string, OperResult>(); | ||||||
|  |  | ||||||
|  |             // 遍历写入信息列表 | ||||||
|  |             foreach (var (deviceVariable, jToken) in writeInfoLists) | ||||||
|             { |             { | ||||||
|                 // 提取原始数据 |                 // 检查是否有写入表达式 | ||||||
|                 object rawdata = jToken.GetObjectFromJToken(); |                 if (!string.IsNullOrEmpty(deviceVariable.WriteExpressions)) | ||||||
|                 try |  | ||||||
|                 { |                 { | ||||||
|                     // 根据写入表达式转换数据 |                     // 提取原始数据 | ||||||
|                     object data = deviceVariable.WriteExpressions.GetExpressionsResult(rawdata, LogMessage); |                     object rawdata = jToken.GetObjectFromJToken(); | ||||||
|                     // 将转换后的数据重新赋值给写入信息列表 |                     try | ||||||
|                     writeInfoLists[deviceVariable] = JTokenUtil.GetJTokenFromObj(data); |                     { | ||||||
|                 } |                         // 根据写入表达式转换数据 | ||||||
|                 catch (Exception ex) |                         object data = deviceVariable.WriteExpressions.GetExpressionsResult(rawdata, @this.LogMessage); | ||||||
|                 { |                         // 将转换后的数据重新赋值给写入信息列表 | ||||||
|                     // 如果转换失败,则记录错误信息 |                         writeInfoLists[deviceVariable] = JTokenUtil.GetJTokenFromObj(data); | ||||||
|                     results.Add(deviceVariable.Name, new OperResult(string.Format(AppResource.WriteExpressionsError, deviceVariable.Name, deviceVariable.WriteExpressions, ex.Message), ex)); |                     } | ||||||
|  |                     catch (Exception ex) | ||||||
|  |                     { | ||||||
|  |                         // 如果转换失败,则记录错误信息 | ||||||
|  |                         results.Add(deviceVariable.Name, new OperResult(string.Format(AppResource.WriteExpressionsError, deviceVariable.Name, deviceVariable.WriteExpressions, ex.Message), ex)); | ||||||
|  |                     } | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|         } |  | ||||||
|  |  | ||||||
|         var writePList = writeInfoLists.Where(a => !CurrentDevice.VariableScriptReads.Select(a => a.VariableRuntime).Any(b => a.Key.Name == b.Name)); |             var writePList = writeInfoLists.Where(a => !@this.CurrentDevice.VariableScriptReads.Select(a => a.VariableRuntime).Any(b => a.Key.Name == b.Name)); | ||||||
|         var writeSList = writeInfoLists.Where(a => CurrentDevice.VariableScriptReads.Select(a => a.VariableRuntime).Any(b => a.Key.Name == b.Name)); |             var writeSList = writeInfoLists.Where(a => @this.CurrentDevice.VariableScriptReads.Select(a => a.VariableRuntime).Any(b => a.Key.Name == b.Name)); | ||||||
|  |  | ||||||
|         DateTime now = DateTime.Now; |             DateTime now = DateTime.Now; | ||||||
|         foreach (var item in writeSList) |             foreach (var item in writeSList) | ||||||
|         { |             { | ||||||
|             results.TryAdd(item.Key.Name, item.Key.SetValue(item.Value, now)); |                 results.TryAdd(item.Key.Name, item.Key.SetValue(item.Value, now)); | ||||||
|         } |             } | ||||||
|  |  | ||||||
|         // 过滤掉转换失败的变量,只保留写入成功的变量进行写入操作 |             // 过滤掉转换失败的变量,只保留写入成功的变量进行写入操作 | ||||||
|         var results1 = await WriteValuesAsync(writePList |             var results1 = await @this.WriteValuesAsync(writePList | ||||||
|             .Where(a => !results.Any(b => b.Key == a.Key.Name)) |                 .Where(a => !results.Any(b => b.Key == a.Key.Name)) | ||||||
|             .ToDictionary(item => item.Key, item => item.Value), |                 .ToDictionary(item => item.Key, item => item.Value), | ||||||
|             cancellationToken).ConfigureAwait(false); |                 cancellationToken).ConfigureAwait(false); | ||||||
|  |  | ||||||
|         // 将转换失败的变量和写入成功的变量的操作结果合并到结果字典中 |             // 将转换失败的变量和写入成功的变量的操作结果合并到结果字典中 | ||||||
|  |  | ||||||
|         return new Dictionary<string, Dictionary<string, IOperResult>>() |             return new Dictionary<string, Dictionary<string, IOperResult>>() | ||||||
|         { |         { | ||||||
|             { |             { | ||||||
|                 DeviceName , |                @this. DeviceName , | ||||||
|                 results.Concat(results1).ToDictionary(a => a.Key, a => (IOperResult)a.Value) |                 results.Concat(results1).ToDictionary(a => a.Key, a => (IOperResult)a.Value) | ||||||
|             } |             } | ||||||
|         }; |         }; | ||||||
|  |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /// <summary> |     /// <summary> | ||||||
| @@ -900,56 +923,61 @@ public abstract partial class CollectBase : DriverBase | |||||||
|     /// <param name="isRead">指示是否为读取操作</param> |     /// <param name="isRead">指示是否为读取操作</param> | ||||||
|     /// <param name="cancellationToken">取消操作的通知</param> |     /// <param name="cancellationToken">取消操作的通知</param> | ||||||
|     /// <returns>操作结果,包含执行方法的结果</returns> |     /// <returns>操作结果,包含执行方法的结果</returns> | ||||||
|     protected virtual async ValueTask<OperResult<object>> InvokeMethodAsync(VariableMethod variableMethod, string? value = null, bool isRead = true, CancellationToken cancellationToken = default) |     protected virtual ValueTask<OperResult<object>> InvokeMethodAsync(VariableMethod variableMethod, string? value = null, bool isRead = true, CancellationToken cancellationToken = default) | ||||||
|     { |     { | ||||||
|         try |         return InvokeMethodAsync(this, variableMethod, value, isRead, cancellationToken); | ||||||
|  |  | ||||||
|  |         static async PooledValueTask<OperResult<object>> InvokeMethodAsync(CollectBase @this, VariableMethod variableMethod, string? value, bool isRead, CancellationToken cancellationToken) | ||||||
|         { |         { | ||||||
|             // 初始化操作结果 |             try | ||||||
|             OperResult<object> result = new OperResult<object>(); |  | ||||||
|  |  | ||||||
|             // 获取要执行的方法 |  | ||||||
|             var method = variableMethod.MethodInfo; |  | ||||||
|  |  | ||||||
|             // 如果方法未找到,则返回错误结果 |  | ||||||
|             if (method == null) |  | ||||||
|             { |             { | ||||||
|                 result.OperCode = 999; |                 // 初始化操作结果 | ||||||
|                 result.ErrorMessage = string.Format(AppResource.MethodNotNull, variableMethod.Variable.Name, variableMethod.Variable.OtherMethod); |                 OperResult<object> result = new OperResult<object>(); | ||||||
|                 return result; |  | ||||||
|             } |  | ||||||
|             else |  | ||||||
|             { |  | ||||||
|                 // 调用方法并获取结果 |  | ||||||
|                 var data = await variableMethod.InvokeMethodAsync(this, value, cancellationToken).ConfigureAwait(false); |  | ||||||
|  |  | ||||||
|                 result = data.GetOperResult(); |                 // 获取要执行的方法 | ||||||
|  |                 var method = variableMethod.MethodInfo; | ||||||
|  |  | ||||||
|                 // 如果方法有返回值,并且是读取操作 |                 // 如果方法未找到,则返回错误结果 | ||||||
|                 if (method.HasReturn && isRead) |                 if (method == null) | ||||||
|                 { |                 { | ||||||
|                     var time = DateTime.Now; |                     result.OperCode = 999; | ||||||
|                     if (result.IsSuccess == true) |                     result.ErrorMessage = string.Format(AppResource.MethodNotNull, variableMethod.Variable.Name, variableMethod.Variable.OtherMethod); | ||||||
|                     { |                     return result; | ||||||
|                         // 将结果序列化并设置到变量中 |                 } | ||||||
|                         var variableResult = variableMethod.Variable.SetValue(result.Content, time); |                 else | ||||||
|                         if (!variableResult.IsSuccess) |                 { | ||||||
|                             variableMethod.LastErrorMessage = result.ErrorMessage; |                     // 调用方法并获取结果 | ||||||
|                     } |                     var data = await variableMethod.InvokeMethodAsync(@this, value, cancellationToken).ConfigureAwait(false); | ||||||
|                     else |  | ||||||
|                     { |                     result = data.GetOperResult(); | ||||||
|                         // 如果读取操作失败,则将变量标记为离线 |  | ||||||
|                         var variableResult = variableMethod.Variable.SetValue(null, time, isOnline: false); |                     // 如果方法有返回值,并且是读取操作 | ||||||
|                         if (!variableResult.IsSuccess) |                     if (method.HasReturn && isRead) | ||||||
|                             variableMethod.LastErrorMessage = result.ErrorMessage; |                     { | ||||||
|                     } |                         var time = DateTime.Now; | ||||||
|  |                         if (result.IsSuccess == true) | ||||||
|  |                         { | ||||||
|  |                             // 将结果序列化并设置到变量中 | ||||||
|  |                             var variableResult = variableMethod.Variable.SetValue(result.Content, time); | ||||||
|  |                             if (!variableResult.IsSuccess) | ||||||
|  |                                 variableMethod.LastErrorMessage = result.ErrorMessage; | ||||||
|  |                         } | ||||||
|  |                         else | ||||||
|  |                         { | ||||||
|  |                             // 如果读取操作失败,则将变量标记为离线 | ||||||
|  |                             var variableResult = variableMethod.Variable.SetValue(null, time, isOnline: false); | ||||||
|  |                             if (!variableResult.IsSuccess) | ||||||
|  |                                 variableMethod.LastErrorMessage = result.ErrorMessage; | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                     return result; | ||||||
|                 } |                 } | ||||||
|                 return result; |  | ||||||
|             } |             } | ||||||
|         } |             catch (Exception ex) | ||||||
|         catch (Exception ex) |             { | ||||||
|         { |                 // 捕获异常并返回错误结果 | ||||||
|             // 捕获异常并返回错误结果 |                 return new OperResult<object>(ex); | ||||||
|             return new OperResult<object>(ex); |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -81,72 +81,77 @@ public abstract class CollectFoundationBase : CollectBase | |||||||
|     } |     } | ||||||
|  |  | ||||||
|  |  | ||||||
|     protected override async Task TestOnline(object? state, CancellationToken cancellationToken) |     protected override ValueTask TestOnline(object? state, CancellationToken cancellationToken) | ||||||
|     { |     { | ||||||
|         if (FoundationDevice != null) |         return TestOnline(this, cancellationToken); | ||||||
|  |  | ||||||
|  |  | ||||||
|  |         static async PooledValueTask TestOnline(CollectFoundationBase @this, CancellationToken cancellationToken) | ||||||
|         { |         { | ||||||
|             if (!FoundationDevice.OnLine) |             if (@this.FoundationDevice != null) | ||||||
|             { |             { | ||||||
|                 if (!FoundationDevice.DisposedValue || FoundationDevice.Channel?.DisposedValue != false) return; |                 if (!@this.FoundationDevice.OnLine) | ||||||
|                 Exception exception = null; |  | ||||||
|                 try |  | ||||||
|                 { |                 { | ||||||
|                     if (!cancellationToken.IsCancellationRequested) |                     if (!@this.FoundationDevice.DisposedValue || @this.FoundationDevice.Channel?.DisposedValue != false) return; | ||||||
|  |                     Exception exception = null; | ||||||
|  |                     try | ||||||
|                     { |                     { | ||||||
|                         if (!FoundationDevice.DisposedValue || FoundationDevice.Channel?.DisposedValue != false) return; |                         if (!cancellationToken.IsCancellationRequested) | ||||||
|  |  | ||||||
|                         await FoundationDevice.ConnectAsync(cancellationToken).ConfigureAwait(false); |  | ||||||
|  |  | ||||||
|                         if (CurrentDevice.DeviceStatusChangeTime < TimerX.Now.AddMinutes(-1)) |  | ||||||
|                         { |                         { | ||||||
|                             await Task.Delay(30000, cancellationToken).ConfigureAwait(false); |                             if (!@this.FoundationDevice.DisposedValue || @this.FoundationDevice.Channel?.DisposedValue != false) return; | ||||||
|  |  | ||||||
|  |                             await @this.FoundationDevice.ConnectAsync(cancellationToken).ConfigureAwait(false); | ||||||
|  |  | ||||||
|  |                             if (@this.CurrentDevice.DeviceStatusChangeTime < TimerX.Now.AddMinutes(-1)) | ||||||
|  |                             { | ||||||
|  |                                 await Task.Delay(30000, cancellationToken).ConfigureAwait(false); | ||||||
|  |                             } | ||||||
|                         } |                         } | ||||||
|                     } |                     } | ||||||
|                 } |                     catch (OperationCanceledException) | ||||||
|                 catch (OperationCanceledException) |  | ||||||
|                 { |  | ||||||
|                 } |  | ||||||
|                 catch (Exception ex) |  | ||||||
|                 { |  | ||||||
|                     exception = ex; |  | ||||||
|                 } |  | ||||||
|                 if (cancellationToken.IsCancellationRequested) |  | ||||||
|                 { |  | ||||||
|                     return; |  | ||||||
|                 } |  | ||||||
|                 if (FoundationDevice.OnLine == false && exception != null) |  | ||||||
|                 { |  | ||||||
|                     foreach (var item in CurrentDevice.VariableSourceReads) |  | ||||||
|                     { |                     { | ||||||
|                         if (item.LastErrorMessage != exception.Message) |  | ||||||
|                         { |  | ||||||
|                             if (!cancellationToken.IsCancellationRequested) |  | ||||||
|                                 LogMessage?.LogWarning(exception, string.Format(AppResource.CollectFail, DeviceName, item?.RegisterAddress, item?.Length, exception.Message)); |  | ||||||
|                         } |  | ||||||
|                         item.LastErrorMessage = exception.Message; |  | ||||||
|                         CurrentDevice.SetDeviceStatus(TimerX.Now, null, exception.Message); |  | ||||||
|                         var time = DateTime.Now; |  | ||||||
|                         item.VariableRuntimes.ForEach(a => a.SetValue(null, time, isOnline: false)); |  | ||||||
|                     } |                     } | ||||||
|                     foreach (var item in CurrentDevice.ReadVariableMethods) |                     catch (Exception ex) | ||||||
|                     { |                     { | ||||||
|                         if (item.LastErrorMessage != exception.Message) |                         exception = ex; | ||||||
|                         { |  | ||||||
|                             if (!cancellationToken.IsCancellationRequested) |  | ||||||
|                                 LogMessage?.LogWarning(exception, string.Format(AppResource.MethodFail, DeviceName, item.MethodInfo.Name, exception.Message)); |  | ||||||
|                         } |  | ||||||
|                         item.LastErrorMessage = exception.Message; |  | ||||||
|                         CurrentDevice.SetDeviceStatus(TimerX.Now, null, exception.Message); |  | ||||||
|                         var time = DateTime.Now; |  | ||||||
|                         item.Variable.SetValue(null, time, isOnline: false); |  | ||||||
|                     } |                     } | ||||||
|  |                     if (cancellationToken.IsCancellationRequested) | ||||||
|  |                     { | ||||||
|  |                         return; | ||||||
|  |                     } | ||||||
|  |                     if (@this.FoundationDevice.OnLine == false && exception != null) | ||||||
|  |                     { | ||||||
|  |                         foreach (var item in @this.CurrentDevice.VariableSourceReads) | ||||||
|  |                         { | ||||||
|  |                             if (item.LastErrorMessage != exception.Message) | ||||||
|  |                             { | ||||||
|  |                                 if (!cancellationToken.IsCancellationRequested) | ||||||
|  |                                     @this.LogMessage?.LogWarning(exception, string.Format(AppResource.CollectFail, @this.DeviceName, item?.RegisterAddress, item?.Length, exception.Message)); | ||||||
|  |                             } | ||||||
|  |                             item.LastErrorMessage = exception.Message; | ||||||
|  |                             @this.CurrentDevice.SetDeviceStatus(TimerX.Now, null, exception.Message); | ||||||
|  |                             var time = DateTime.Now; | ||||||
|  |                             item.VariableRuntimes.ForEach(a => a.SetValue(null, time, isOnline: false)); | ||||||
|  |                         } | ||||||
|  |                         foreach (var item in @this.CurrentDevice.ReadVariableMethods) | ||||||
|  |                         { | ||||||
|  |                             if (item.LastErrorMessage != exception.Message) | ||||||
|  |                             { | ||||||
|  |                                 if (!cancellationToken.IsCancellationRequested) | ||||||
|  |                                     @this.LogMessage?.LogWarning(exception, string.Format(AppResource.MethodFail, @this.DeviceName, item.MethodInfo.Name, exception.Message)); | ||||||
|  |                             } | ||||||
|  |                             item.LastErrorMessage = exception.Message; | ||||||
|  |                             @this.CurrentDevice.SetDeviceStatus(TimerX.Now, null, exception.Message); | ||||||
|  |                             var time = DateTime.Now; | ||||||
|  |                             item.Variable.SetValue(null, time, isOnline: false); | ||||||
|  |                         } | ||||||
|  |  | ||||||
|                     return; |                     } | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|  |  | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         return; |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -309,46 +314,51 @@ public abstract class CollectFoundationBase : CollectBase | |||||||
|     /// 批量写入变量值,需返回变量名称/结果,注意非通用设备需重写 |     /// 批量写入变量值,需返回变量名称/结果,注意非通用设备需重写 | ||||||
|     /// </summary> |     /// </summary> | ||||||
|     /// <returns></returns> |     /// <returns></returns> | ||||||
|     protected override async ValueTask<Dictionary<string, OperResult>> WriteValuesAsync(Dictionary<VariableRuntime, JToken> writeInfoLists, CancellationToken cancellationToken) |     protected override ValueTask<Dictionary<string, OperResult>> WriteValuesAsync(Dictionary<VariableRuntime, JToken> writeInfoLists, CancellationToken cancellationToken) | ||||||
|     { |     { | ||||||
|         using var writeLock = await ReadWriteLock.WriterLockAsync(cancellationToken).ConfigureAwait(false); |         return WriteValuesAsync(this, writeInfoLists, cancellationToken); | ||||||
|         // 检查协议是否为空,如果为空则抛出异常 |  | ||||||
|         if (FoundationDevice == null) |  | ||||||
|             throw new NotSupportedException(); |  | ||||||
|  |  | ||||||
|         // 创建用于存储操作结果的并发字典 |         static async PooledValueTask<Dictionary<string, OperResult>> WriteValuesAsync(CollectFoundationBase @this, Dictionary<VariableRuntime, JToken> writeInfoLists, CancellationToken cancellationToken) | ||||||
|         NonBlockingDictionary<string, OperResult> operResults = new(); |  | ||||||
|         // 使用并发方式遍历写入信息列表,并进行异步写入操作 |  | ||||||
|         await writeInfoLists.ParallelForEachAsync(async (writeInfo, cancellationToken) => |  | ||||||
|         { |         { | ||||||
|             try |             using var writeLock = await @this.ReadWriteLock.WriterLockAsync(cancellationToken).ConfigureAwait(false); | ||||||
|  |             // 检查协议是否为空,如果为空则抛出异常 | ||||||
|  |             if (@this.FoundationDevice == null) | ||||||
|  |                 throw new NotSupportedException(); | ||||||
|  |  | ||||||
|  |             // 创建用于存储操作结果的并发字典 | ||||||
|  |             NonBlockingDictionary<string, OperResult> operResults = new(); | ||||||
|  |             // 使用并发方式遍历写入信息列表,并进行异步写入操作 | ||||||
|  |             await writeInfoLists.ParallelForEachAsync(async (writeInfo, cancellationToken) => | ||||||
|             { |             { | ||||||
|  |                 try | ||||||
|                 // 调用协议的写入方法,将写入信息中的数据写入到对应的寄存器地址,并获取操作结果 |  | ||||||
|                 var result = await FoundationDevice.WriteJTokenAsync(writeInfo.Key.RegisterAddress, writeInfo.Value, writeInfo.Key.DataType, cancellationToken).ConfigureAwait(false); |  | ||||||
|  |  | ||||||
|                 if (result.IsSuccess) |  | ||||||
|                 { |                 { | ||||||
|                     if (LogMessage?.LogLevel <= TouchSocket.Core.LogLevel.Debug) |  | ||||||
|                         LogMessage?.Debug(string.Format("{0} - Write [{1} - {2} - {3}] data succeeded", DeviceName, writeInfo.Key.RegisterAddress, writeInfo.Value, writeInfo.Key.DataType)); |                     // 调用协议的写入方法,将写入信息中的数据写入到对应的寄存器地址,并获取操作结果 | ||||||
|  |                     var result = await @this.FoundationDevice.WriteJTokenAsync(writeInfo.Key.RegisterAddress, writeInfo.Value, writeInfo.Key.DataType, cancellationToken).ConfigureAwait(false); | ||||||
|  |  | ||||||
|  |                     if (result.IsSuccess) | ||||||
|  |                     { | ||||||
|  |                         if (@this.LogMessage?.LogLevel <= TouchSocket.Core.LogLevel.Debug) | ||||||
|  |                             @this.LogMessage?.Debug(string.Format("{0} - Write [{1} - {2} - {3}] data succeeded", @this.DeviceName, writeInfo.Key.RegisterAddress, writeInfo.Value, writeInfo.Key.DataType)); | ||||||
|  |                     } | ||||||
|  |                     else | ||||||
|  |                     { | ||||||
|  |                         @this.LogMessage?.Warning(string.Format("{0} - Write [{1} - {2} - {3}] data failed {4}", @this.DeviceName, writeInfo.Key.RegisterAddress, writeInfo.Value, writeInfo.Key.DataType, result.ToString())); | ||||||
|  |                     } | ||||||
|  |                     // 将操作结果添加到结果字典中,使用变量名称作为键 | ||||||
|  |                     operResults.TryAdd(writeInfo.Key.Name, result); | ||||||
|                 } |                 } | ||||||
|                 else |                 catch (Exception ex) | ||||||
|                 { |                 { | ||||||
|                     LogMessage?.Warning(string.Format("{0} - Write [{1} - {2} - {3}] data failed {4}", DeviceName, writeInfo.Key.RegisterAddress, writeInfo.Value, writeInfo.Key.DataType, result.ToString())); |                     operResults.TryAdd(writeInfo.Key.Name, new(ex)); | ||||||
|                 } |                 } | ||||||
|                 // 将操作结果添加到结果字典中,使用变量名称作为键 |             }, @this.CollectProperties.MaxConcurrentCount, cancellationToken).ConfigureAwait(false); | ||||||
|                 operResults.TryAdd(writeInfo.Key.Name, result); |  | ||||||
|             } |  | ||||||
|             catch (Exception ex) |  | ||||||
|             { |  | ||||||
|                 operResults.TryAdd(writeInfo.Key.Name, new(ex)); |  | ||||||
|             } |  | ||||||
|         }, CollectProperties.MaxConcurrentCount, cancellationToken).ConfigureAwait(false); |  | ||||||
|  |  | ||||||
|         await Check(writeInfoLists, operResults, cancellationToken).ConfigureAwait(false); |             await @this.Check(writeInfoLists, operResults, cancellationToken).ConfigureAwait(false); | ||||||
|  |  | ||||||
|         // 返回包含操作结果的字典 |             // 返回包含操作结果的字典 | ||||||
|         return new Dictionary<string, OperResult>(operResults); |             return new Dictionary<string, OperResult>(operResults); | ||||||
|  |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |  | ||||||
|   | |||||||
| @@ -10,6 +10,8 @@ | |||||||
|  |  | ||||||
| using BootstrapBlazor.Components; | using BootstrapBlazor.Components; | ||||||
|  |  | ||||||
|  | using PooledAwait; | ||||||
|  |  | ||||||
| using System.Collections.Concurrent; | using System.Collections.Concurrent; | ||||||
|  |  | ||||||
| using ThingsGateway.Extension.Generic; | using ThingsGateway.Extension.Generic; | ||||||
| @@ -89,38 +91,63 @@ public static class GlobalData | |||||||
|  |  | ||||||
|     public static event PluginEventHandler? PluginEventHandler; |     public static event PluginEventHandler? PluginEventHandler; | ||||||
|  |  | ||||||
|     public static async Task<IEnumerable<ChannelRuntime>> GetCurrentUserChannels() |     public static Task<IEnumerable<ChannelRuntime>> GetCurrentUserChannels() | ||||||
|     { |     { | ||||||
|         var dataScope = await GlobalData.SysUserService.GetCurrentUserDataScopeAsync().ConfigureAwait(false); |         return GetCurrentUserChannels(); | ||||||
|         return ReadOnlyIdChannels.WhereIf(dataScope != null && dataScope?.Count > 0, u => dataScope.Contains(u.Value.CreateOrgId))//在指定机构列表查询 |  | ||||||
|           .WhereIf(dataScope?.Count == 0, u => u.Value.CreateUserId == UserManager.UserId).Select(a => a.Value); |         static async PooledTask<IEnumerable<ChannelRuntime>> GetCurrentUserChannels() | ||||||
|  |         { | ||||||
|  |             var dataScope = await GlobalData.SysUserService.GetCurrentUserDataScopeAsync().ConfigureAwait(false); | ||||||
|  |             return ReadOnlyIdChannels.WhereIf(dataScope != null && dataScope?.Count > 0, u => dataScope.Contains(u.Value.CreateOrgId))//在指定机构列表查询 | ||||||
|  |               .WhereIf(dataScope?.Count == 0, u => u.Value.CreateUserId == UserManager.UserId).Select(a => a.Value); | ||||||
|  |         } | ||||||
|     } |     } | ||||||
|     public static async Task<IEnumerable<DeviceRuntime>> GetCurrentUserDevices() |     public static Task<IEnumerable<DeviceRuntime>> GetCurrentUserDevices() | ||||||
|     { |     { | ||||||
|         var dataScope = await GlobalData.SysUserService.GetCurrentUserDataScopeAsync().ConfigureAwait(false); |         return GetCurrentUserDevices(); | ||||||
|         return ReadOnlyIdDevices.WhereIf(dataScope != null && dataScope?.Count > 0, u => dataScope.Contains(u.Value.CreateOrgId))//在指定机构列表查询 |  | ||||||
|           .WhereIf(dataScope?.Count == 0, u => u.Value.CreateUserId == UserManager.UserId).Select(a => a.Value); |         static async PooledTask<IEnumerable<DeviceRuntime>> GetCurrentUserDevices() | ||||||
|  |         { | ||||||
|  |             var dataScope = await GlobalData.SysUserService.GetCurrentUserDataScopeAsync().ConfigureAwait(false); | ||||||
|  |             return ReadOnlyIdDevices.WhereIf(dataScope != null && dataScope?.Count > 0, u => dataScope.Contains(u.Value.CreateOrgId))//在指定机构列表查询 | ||||||
|  |               .WhereIf(dataScope?.Count == 0, u => u.Value.CreateUserId == UserManager.UserId).Select(a => a.Value); | ||||||
|  |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     public static async Task<IEnumerable<VariableRuntime>> GetCurrentUserIdVariables() |     public static Task<IEnumerable<VariableRuntime>> GetCurrentUserIdVariables() | ||||||
|     { |     { | ||||||
|         var dataScope = await GlobalData.SysUserService.GetCurrentUserDataScopeAsync().ConfigureAwait(false); |         return GetCurrentUserIdVariables(); | ||||||
|         return IdVariables.Where(a => a.Value.IsInternalMemoryVariable == false).WhereIf(dataScope != null && dataScope?.Count > 0, u => dataScope.Contains(u.Value.CreateOrgId))//在指定机构列表查询 |  | ||||||
|           .WhereIf(dataScope?.Count == 0, u => u.Value.CreateUserId == UserManager.UserId).Select(a => a.Value); |         static async PooledTask<IEnumerable<VariableRuntime>> GetCurrentUserIdVariables() | ||||||
|  |         { | ||||||
|  |             var dataScope = await GlobalData.SysUserService.GetCurrentUserDataScopeAsync().ConfigureAwait(false); | ||||||
|  |             return IdVariables.Where(a => a.Value.IsInternalMemoryVariable == false).WhereIf(dataScope != null && dataScope?.Count > 0, u => dataScope.Contains(u.Value.CreateOrgId))//在指定机构列表查询 | ||||||
|  |               .WhereIf(dataScope?.Count == 0, u => u.Value.CreateUserId == UserManager.UserId).Select(a => a.Value); | ||||||
|  |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     public static async Task<IEnumerable<AlarmVariable>> GetCurrentUserRealAlarmVariablesAsync() |     public static Task<IEnumerable<AlarmVariable>> GetCurrentUserRealAlarmVariablesAsync() | ||||||
|     { |     { | ||||||
|         var dataScope = await GlobalData.SysUserService.GetCurrentUserDataScopeAsync().ConfigureAwait(false); |         return GetCurrentUserRealAlarmVariablesAsync(); | ||||||
|         return RealAlarmIdVariables.WhereIf(dataScope != null && dataScope?.Count > 0, u => dataScope.Contains(u.Value.CreateOrgId))//在指定机构列表查询 |  | ||||||
|           .WhereIf(dataScope?.Count == 0, u => u.Value.CreateUserId == UserManager.UserId).Select(a => a.Value); |         static async PooledTask<IEnumerable<AlarmVariable>> GetCurrentUserRealAlarmVariablesAsync() | ||||||
|  |         { | ||||||
|  |             var dataScope = await GlobalData.SysUserService.GetCurrentUserDataScopeAsync().ConfigureAwait(false); | ||||||
|  |             return RealAlarmIdVariables.WhereIf(dataScope != null && dataScope?.Count > 0, u => dataScope.Contains(u.Value.CreateOrgId))//在指定机构列表查询 | ||||||
|  |               .WhereIf(dataScope?.Count == 0, u => u.Value.CreateUserId == UserManager.UserId).Select(a => a.Value); | ||||||
|  |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     public static async Task<IEnumerable<VariableRuntime>> GetCurrentUserAlarmEnableVariables() |     public static Task<IEnumerable<VariableRuntime>> GetCurrentUserAlarmEnableVariables() | ||||||
|     { |     { | ||||||
|         var dataScope = await GlobalData.SysUserService.GetCurrentUserDataScopeAsync().ConfigureAwait(false); |         return GetCurrentUserAlarmEnableVariables(); | ||||||
|         return AlarmEnableIdVariables.Where(a => a.Value.IsInternalMemoryVariable == false).WhereIf(dataScope != null && dataScope?.Count > 0, u => dataScope.Contains(u.Value.CreateOrgId))//在指定机构列表查询 |  | ||||||
|           .WhereIf(dataScope?.Count == 0, u => u.Value.CreateUserId == UserManager.UserId).Select(a => a.Value); |         static async PooledTask<IEnumerable<VariableRuntime>> GetCurrentUserAlarmEnableVariables() | ||||||
|  |         { | ||||||
|  |             var dataScope = await GlobalData.SysUserService.GetCurrentUserDataScopeAsync().ConfigureAwait(false); | ||||||
|  |             return AlarmEnableIdVariables.Where(a => a.Value.IsInternalMemoryVariable == false).WhereIf(dataScope != null && dataScope?.Count > 0, u => dataScope.Contains(u.Value.CreateOrgId))//在指定机构列表查询 | ||||||
|  |               .WhereIf(dataScope?.Count == 0, u => u.Value.CreateUserId == UserManager.UserId).Select(a => a.Value); | ||||||
|  |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     public static bool ContainsVariable(long businessDeviceId, VariableRuntime a) |     public static bool ContainsVariable(long businessDeviceId, VariableRuntime a) | ||||||
|   | |||||||
| @@ -8,6 +8,8 @@ | |||||||
| //  QQ群:605534569 | //  QQ群:605534569 | ||||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||||
|  |  | ||||||
|  | using PooledAwait; | ||||||
|  |  | ||||||
| using TouchSocket.Core; | using TouchSocket.Core; | ||||||
|  |  | ||||||
| namespace ThingsGateway.Gateway.Application; | namespace ThingsGateway.Gateway.Application; | ||||||
| @@ -51,42 +53,47 @@ public class VariableMethod | |||||||
|     /// <param name="value">以,逗号分割的参数</param> |     /// <param name="value">以,逗号分割的参数</param> | ||||||
|     /// <param name="cancellationToken">取消令箭</param> |     /// <param name="cancellationToken">取消令箭</param> | ||||||
|     /// <returns></returns> |     /// <returns></returns> | ||||||
|     public async ValueTask<IOperResult> InvokeMethodAsync(object driverBase, string? value = null, CancellationToken cancellationToken = default) |     public ValueTask<IOperResult> InvokeMethodAsync(object driverBase, string? value = null, CancellationToken cancellationToken = default) | ||||||
|     { |     { | ||||||
|         try |         return InvokeMethodAsync(this, driverBase, value, cancellationToken); | ||||||
|  |  | ||||||
|  |         static async PooledValueTask<IOperResult> InvokeMethodAsync(VariableMethod @this, object driverBase, string? value, CancellationToken cancellationToken) | ||||||
|         { |         { | ||||||
|             object?[]? os = null; |             try | ||||||
|             if (value == null && OS == null) |  | ||||||
|             { |             { | ||||||
|                 //默认的参数 |                 object?[]? os = null; | ||||||
|                 var addresss = Variable.RegisterAddress.SplitOS(); |                 if (value == null && @this.OS == null) | ||||||
|                 //通过逗号分割,并且合并参数 |                 { | ||||||
|                 var strs = addresss; |                     //默认的参数 | ||||||
|  |                     var addresss = @this.Variable.RegisterAddress.SplitOS(); | ||||||
|  |                     //通过逗号分割,并且合并参数 | ||||||
|  |                     var strs = addresss; | ||||||
|  |  | ||||||
|                 OS = GetOS(strs, cancellationToken); |                     @this.OS = @this.GetOS(strs, cancellationToken); | ||||||
|                 os = OS; |                     os = @this.OS; | ||||||
|  |                 } | ||||||
|  |                 else | ||||||
|  |                 { | ||||||
|  |                     var addresss = @this.Variable.RegisterAddress.SplitOS(); | ||||||
|  |                     var values = value.SplitOS(); | ||||||
|  |                     //通过分号分割,并且合并参数 | ||||||
|  |                     var strs = addresss.Concat(values).ToList(); | ||||||
|  |                     os = @this.GetOS(strs, cancellationToken); | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 dynamic result; | ||||||
|  |  | ||||||
|  |                 result = await @this.MethodInfo.InvokeAsync(driverBase, os).ConfigureAwait(false); | ||||||
|  |                 if (@this.MethodInfo.HasReturn) | ||||||
|  |                 { | ||||||
|  |                     return result; | ||||||
|  |                 } | ||||||
|  |                 return OperResult.Success; | ||||||
|             } |             } | ||||||
|             else |             catch (Exception ex) | ||||||
|             { |             { | ||||||
|                 var addresss = Variable.RegisterAddress.SplitOS(); |                 return new OperResult(ex); | ||||||
|                 var values = value.SplitOS(); |  | ||||||
|                 //通过分号分割,并且合并参数 |  | ||||||
|                 var strs = addresss.Concat(values).ToList(); |  | ||||||
|                 os = GetOS(strs, cancellationToken); |  | ||||||
|             } |             } | ||||||
|  |  | ||||||
|             dynamic result; |  | ||||||
|  |  | ||||||
|             result = await MethodInfo.InvokeAsync(driverBase, os).ConfigureAwait(false); |  | ||||||
|             if (MethodInfo.HasReturn) |  | ||||||
|             { |  | ||||||
|                 return result; |  | ||||||
|             } |  | ||||||
|             return OperResult.Success; |  | ||||||
|         } |  | ||||||
|         catch (Exception ex) |  | ||||||
|         { |  | ||||||
|             return new OperResult(ex); |  | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -12,6 +12,8 @@ using BootstrapBlazor.Components; | |||||||
|  |  | ||||||
| using Newtonsoft.Json.Linq; | using Newtonsoft.Json.Linq; | ||||||
|  |  | ||||||
|  | using PooledAwait; | ||||||
|  |  | ||||||
| using Riok.Mapperly.Abstractions; | using Riok.Mapperly.Abstractions; | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -448,13 +450,18 @@ public partial class VariableRuntime : Variable | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     /// <inheritdoc/> |     /// <inheritdoc/> | ||||||
|     public virtual async ValueTask<IOperResult> RpcAsync(string value, string executive = "brower", CancellationToken cancellationToken = default) |     public virtual ValueTask<IOperResult> RpcAsync(string value, string executive = "brower", CancellationToken cancellationToken = default) | ||||||
|     { |     { | ||||||
|         var data = await GlobalData.RpcService.InvokeDeviceMethodAsync(executive, new Dictionary<string, Dictionary<string, string>>() |         return RpcAsync(DeviceName, Name, value, executive, cancellationToken); | ||||||
|  |  | ||||||
|  |         static async PooledValueTask<IOperResult> RpcAsync(string deviceName, string name, string value, string executive, CancellationToken cancellationToken) | ||||||
|         { |         { | ||||||
|             { DeviceName, new Dictionary<string, string>()  {   { Name,value} }  } |             var data = await GlobalData.RpcService.InvokeDeviceMethodAsync(executive, new Dictionary<string, Dictionary<string, string>>() | ||||||
|  |         { | ||||||
|  |             { deviceName, new Dictionary<string, string>()  {   { name,value} }  } | ||||||
|         }, cancellationToken).ConfigureAwait(false); |         }, cancellationToken).ConfigureAwait(false); | ||||||
|         return data.FirstOrDefault().Value.FirstOrDefault().Value; |             return data.FirstOrDefault().Value.FirstOrDefault().Value; | ||||||
|  |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     public void SetErrorMessage(string lastErrorMessage) |     public void SetErrorMessage(string lastErrorMessage) | ||||||
|   | |||||||
| @@ -13,6 +13,8 @@ using BootstrapBlazor.Components; | |||||||
| using Microsoft.Extensions.Hosting; | using Microsoft.Extensions.Hosting; | ||||||
| using Microsoft.Extensions.Logging; | using Microsoft.Extensions.Logging; | ||||||
|  |  | ||||||
|  | using PooledAwait; | ||||||
|  |  | ||||||
| using ThingsGateway.Blazor.Diagrams.Core; | using ThingsGateway.Blazor.Diagrams.Core; | ||||||
| using ThingsGateway.Blazor.Diagrams.Core.Models; | using ThingsGateway.Blazor.Diagrams.Core.Models; | ||||||
| using ThingsGateway.NewLife; | using ThingsGateway.NewLife; | ||||||
| @@ -139,68 +141,76 @@ internal sealed class RulesEngineHostedService : BackgroundService, IRulesEngine | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private static async Task Analysis(NodeModel targetNode, NodeInput input, RulesLog rulesLog, CancellationToken cancellationToken) |     private static Task Analysis(NodeModel targetNode, NodeInput input, RulesLog rulesLog, CancellationToken cancellationToken) | ||||||
|     { |     { | ||||||
|         if (targetNode is INode node) |         return Analysis(targetNode, input, rulesLog, cancellationToken); | ||||||
|         { |  | ||||||
|             node.Logger = rulesLog.Log; |  | ||||||
|             node.RulesEngineName = rulesLog.Rules.Name; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         try |  | ||||||
|  |         static async PooledTask Analysis(NodeModel targetNode, NodeInput input, RulesLog rulesLog, CancellationToken cancellationToken) | ||||||
|         { |         { | ||||||
|             if (targetNode == null) |             if (targetNode is INode node) | ||||||
|                 return; |  | ||||||
|             if (targetNode is IConditionNode conditionNode) |  | ||||||
|             { |             { | ||||||
|                 var next = await conditionNode.ExecuteAsync(input, cancellationToken).ConfigureAwait(false); |                 node.Logger = rulesLog.Log; | ||||||
|                 if (next) |                 node.RulesEngineName = rulesLog.Rules.Name; | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             try | ||||||
|  |             { | ||||||
|  |                 if (targetNode == null) | ||||||
|  |                     return; | ||||||
|  |                 if (targetNode is IConditionNode conditionNode) | ||||||
|                 { |                 { | ||||||
|                     foreach (var link in targetNode.PortLinks.Where(a => ((a.Target.Model as PortModel)?.Parent) != targetNode)) |                     var next = await conditionNode.ExecuteAsync(input, cancellationToken).ConfigureAwait(false); | ||||||
|  |                     if (next) | ||||||
|                     { |                     { | ||||||
|                         await Analysis((link.Target.Model as PortModel)?.Parent, input, rulesLog, cancellationToken).ConfigureAwait(false); |                         foreach (var link in targetNode.PortLinks.Where(a => ((a.Target.Model as PortModel)?.Parent) != targetNode)) | ||||||
|  |                         { | ||||||
|  |                             await RulesEngineHostedService.Analysis((link.Target.Model as PortModel)?.Parent, input, rulesLog, cancellationToken).ConfigureAwait(false); | ||||||
|  |                         } | ||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
|             } |                 else if (targetNode is IExpressionNode expressionNode) | ||||||
|             else if (targetNode is IExpressionNode expressionNode) |  | ||||||
|             { |  | ||||||
|                 var nodeOutput = await expressionNode.ExecuteAsync(input, cancellationToken).ConfigureAwait(false); |  | ||||||
|                 if (nodeOutput.IsSuccess) |  | ||||||
|                 { |                 { | ||||||
|                     foreach (var link in targetNode.PortLinks.Where(a => ((a.Target.Model as PortModel)?.Parent) != targetNode)) |                     var nodeOutput = await expressionNode.ExecuteAsync(input, cancellationToken).ConfigureAwait(false); | ||||||
|  |                     if (nodeOutput.IsSuccess) | ||||||
|                     { |                     { | ||||||
|                         await Analysis((link.Target.Model as PortModel)?.Parent, new NodeInput() { Value = nodeOutput.Content.Value, }, rulesLog, cancellationToken).ConfigureAwait(false); |                         foreach (var link in targetNode.PortLinks.Where(a => ((a.Target.Model as PortModel)?.Parent) != targetNode)) | ||||||
|  |                         { | ||||||
|  |                             await RulesEngineHostedService.Analysis((link.Target.Model as PortModel)?.Parent, new NodeInput() { Value = nodeOutput.Content.Value, }, rulesLog, cancellationToken).ConfigureAwait(false); | ||||||
|  |                         } | ||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
|             } |                 else if (targetNode is IActuatorNode actuatorNode) | ||||||
|             else if (targetNode is IActuatorNode actuatorNode) |  | ||||||
|             { |  | ||||||
|                 var nodeOutput = await actuatorNode.ExecuteAsync(input, cancellationToken).ConfigureAwait(false); |  | ||||||
|                 if (nodeOutput.IsSuccess) |  | ||||||
|                 { |                 { | ||||||
|                     foreach (var link in targetNode.PortLinks.Where(a => ((a.Target.Model as PortModel)?.Parent) != targetNode)) |                     var nodeOutput = await actuatorNode.ExecuteAsync(input, cancellationToken).ConfigureAwait(false); | ||||||
|  |                     if (nodeOutput.IsSuccess) | ||||||
|                     { |                     { | ||||||
|                         await Analysis((link.Target.Model as PortModel)?.Parent, new NodeInput() { Value = nodeOutput.Content.Value }, rulesLog, cancellationToken).ConfigureAwait(false); |                         foreach (var link in targetNode.PortLinks.Where(a => ((a.Target.Model as PortModel)?.Parent) != targetNode)) | ||||||
|  |                         { | ||||||
|  |                             await RulesEngineHostedService.Analysis((link.Target.Model as PortModel)?.Parent, new NodeInput() { Value = nodeOutput.Content.Value }, rulesLog, cancellationToken).ConfigureAwait(false); | ||||||
|  |                         } | ||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
|             } |                 else if (targetNode is ITriggerNode triggerNode) | ||||||
|             else if (targetNode is ITriggerNode triggerNode) |  | ||||||
|             { |  | ||||||
|                 Func<NodeOutput, CancellationToken, Task> func = (async (a, token) => |  | ||||||
|                 { |                 { | ||||||
|                     foreach (var link in targetNode.PortLinks.Where(a => ((a.Target.Model as PortModel)?.Parent) != targetNode)) |                     Func<NodeOutput, CancellationToken, Task> func = (async (a, token) => | ||||||
|                     { |                     { | ||||||
|                         await Analysis((link.Target.Model as PortModel)?.Parent, new NodeInput() { Value = a.Value }, rulesLog, token).ConfigureAwait(false); |                         foreach (var link in targetNode.PortLinks.Where(a => ((a.Target.Model as PortModel)?.Parent) != targetNode)) | ||||||
|                     } |                         { | ||||||
|                 }); |                             await RulesEngineHostedService.Analysis((link.Target.Model as PortModel)?.Parent, new NodeInput() { Value = a.Value }, rulesLog, token).ConfigureAwait(false); | ||||||
|                 await triggerNode.StartAsync(func, cancellationToken).ConfigureAwait(false); |                         } | ||||||
|  |                     }); | ||||||
|  |                     await triggerNode.StartAsync(func, cancellationToken).ConfigureAwait(false); | ||||||
|  |                 } | ||||||
|             } |             } | ||||||
|         } |             catch (TaskCanceledException) { } | ||||||
|         catch (TaskCanceledException) { } |             catch (OperationCanceledException) { } | ||||||
|         catch (OperationCanceledException) { } |             catch (Exception ex) | ||||||
|         catch (Exception ex) |             { | ||||||
|         { |                 rulesLog.Log?.LogWarning(ex); | ||||||
|             rulesLog.Log?.LogWarning(ex); |             } | ||||||
|  |  | ||||||
|  |             return; | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -321,6 +321,33 @@ public partial class ChannelDeviceTree | |||||||
|         return "enable--text"; |         return "enable--text"; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     private static string GetClass(ChannelDeviceTreeItemStruct item) | ||||||
|  |     { | ||||||
|  |         if (item.TryGetChannelRuntime(out var channelRuntime)) | ||||||
|  |         { | ||||||
|  |             return channelRuntime.DeviceThreadManage != null ? " enable--text" : " disabled--text "; | ||||||
|  |         } | ||||||
|  |         else if (item.TryGetDeviceRuntime(out var deviceRuntime)) | ||||||
|  |         { | ||||||
|  |             if (deviceRuntime.Driver?.DeviceThreadManage != null) | ||||||
|  |             { | ||||||
|  |                 if (deviceRuntime.DeviceStatus == DeviceStatusEnum.OnLine) | ||||||
|  |                 { | ||||||
|  |                     return "green--text"; | ||||||
|  |                 } | ||||||
|  |                 else | ||||||
|  |                 { | ||||||
|  |                     return "red--text"; | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             else | ||||||
|  |             { | ||||||
|  |                 return "disabled--text"; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         return "enable--text"; | ||||||
|  |     } | ||||||
|  |  | ||||||
|     [Inject] |     [Inject] | ||||||
|     DialogService DialogService { get; set; } |     DialogService DialogService { get; set; } | ||||||
|  |  | ||||||
| @@ -1617,7 +1644,7 @@ EventCallback.Factory.Create<MouseEventArgs>(this, async e => | |||||||
|         List<string> ret = new(jsstring.Count); |         List<string> ret = new(jsstring.Count); | ||||||
|         foreach (var str in jsstring) |         foreach (var str in jsstring) | ||||||
|         { |         { | ||||||
|             var item = ChannelDeviceTreeItem.FromJSString(str); |             var item = ChannelDeviceTreeItemStruct.FromJSString(str); | ||||||
|             ret.Add(GetClass(item)); |             ret.Add(GetClass(item)); | ||||||
|         } |         } | ||||||
|         return ret; |         return ret; | ||||||
|   | |||||||
| @@ -149,7 +149,75 @@ public class ChannelDeviceTreeItem : IEqualityComparer<ChannelDeviceTreeItem> | |||||||
|         return $"{channelDeviceTreeItem.ChannelDevicePluginType}.{channelDeviceTreeItem.DeviceRuntimeId}.{channelDeviceTreeItem.ChannelRuntimeId}.{channelDeviceTreeItem.PluginName}.{channelDeviceTreeItem.PluginType}"; |         return $"{channelDeviceTreeItem.ChannelDevicePluginType}.{channelDeviceTreeItem.DeviceRuntimeId}.{channelDeviceTreeItem.ChannelRuntimeId}.{channelDeviceTreeItem.PluginName}.{channelDeviceTreeItem.PluginType}"; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     public static ChannelDeviceTreeItem FromJSString(string jsString) |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  | public struct ChannelDeviceTreeItemStruct | ||||||
|  | { | ||||||
|  |     public long Id { get; set; } | ||||||
|  |     public ChannelDevicePluginTypeEnum ChannelDevicePluginType { get; set; } | ||||||
|  |  | ||||||
|  |     public long DeviceRuntimeId { get; set; } | ||||||
|  |  | ||||||
|  |     public long ChannelRuntimeId { get; set; } | ||||||
|  |     public string PluginName { get; set; } | ||||||
|  |     public PluginTypeEnum? PluginType { get; set; } | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     public bool TryGetDeviceRuntime(out DeviceRuntime deviceRuntime) | ||||||
|  |     { | ||||||
|  |         if (ChannelDevicePluginType == ChannelDevicePluginTypeEnum.Device && DeviceRuntimeId > 0) | ||||||
|  |         { | ||||||
|  |             if (GlobalData.ReadOnlyIdDevices.TryGetValue(DeviceRuntimeId, out deviceRuntime)) | ||||||
|  |             { | ||||||
|  |                 return true; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         deviceRuntime = null; | ||||||
|  |         return false; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public bool TryGetPluginName(out string pluginName) | ||||||
|  |     { | ||||||
|  |         if (ChannelDevicePluginType == ChannelDevicePluginTypeEnum.PluginName) | ||||||
|  |         { | ||||||
|  |             pluginName = PluginName; | ||||||
|  |             return true; | ||||||
|  |         } | ||||||
|  |         else | ||||||
|  |         { | ||||||
|  |             pluginName = default; | ||||||
|  |             return false; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public bool TryGetPluginType(out PluginTypeEnum? pluginType) | ||||||
|  |     { | ||||||
|  |         if (ChannelDevicePluginType == ChannelDevicePluginTypeEnum.PluginType) | ||||||
|  |         { | ||||||
|  |             pluginType = PluginType; | ||||||
|  |             return true; | ||||||
|  |         } | ||||||
|  |         else | ||||||
|  |         { | ||||||
|  |             pluginType = default; | ||||||
|  |             return false; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     public bool TryGetChannelRuntime(out ChannelRuntime channelRuntime) | ||||||
|  |     { | ||||||
|  |         if (ChannelDevicePluginType == ChannelDevicePluginTypeEnum.Channel && ChannelRuntimeId > 0) | ||||||
|  |         { | ||||||
|  |             if (GlobalData.ReadOnlyIdChannels.TryGetValue(ChannelRuntimeId, out channelRuntime)) | ||||||
|  |             { | ||||||
|  |                 return true; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         channelRuntime = null; | ||||||
|  |         return false; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public static ChannelDeviceTreeItemStruct FromJSString(string jsString) | ||||||
|     { |     { | ||||||
|         if (string.IsNullOrWhiteSpace(jsString)) |         if (string.IsNullOrWhiteSpace(jsString)) | ||||||
|             throw new ArgumentNullException(nameof(jsString)); |             throw new ArgumentNullException(nameof(jsString)); | ||||||
| @@ -204,7 +272,7 @@ public class ChannelDeviceTreeItem : IEqualityComparer<ChannelDeviceTreeItem> | |||||||
|             parsedPluginType = tmp; |             parsedPluginType = tmp; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         return new ChannelDeviceTreeItem |         return new ChannelDeviceTreeItemStruct | ||||||
|         { |         { | ||||||
|             ChannelDevicePluginType = pluginType, |             ChannelDevicePluginType = pluginType, | ||||||
|             DeviceRuntimeId = deviceRuntimeId, |             DeviceRuntimeId = deviceRuntimeId, | ||||||
| @@ -215,3 +283,4 @@ public class ChannelDeviceTreeItem : IEqualityComparer<ChannelDeviceTreeItem> | |||||||
|     } |     } | ||||||
|  |  | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -0,0 +1,426 @@ | |||||||
|  | // ------------------------------------------------------------------------------ | ||||||
|  | // 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 | ||||||
|  | // 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 | ||||||
|  | // CSDN博客:https://blog.csdn.net/qq_40374647 | ||||||
|  | // 哔哩哔哩视频:https://space.bilibili.com/94253567 | ||||||
|  | // Gitee源代码仓库:https://gitee.com/RRQM_Home | ||||||
|  | // Github源代码仓库:https://github.com/RRQM | ||||||
|  | // API首页:https://touchsocket.net/ | ||||||
|  | // 交流QQ群:234762506 | ||||||
|  | // 感谢您的下载和使用 | ||||||
|  | // ------------------------------------------------------------------------------ | ||||||
|  |  | ||||||
|  | using BenchmarkDotNet.Attributes; | ||||||
|  |  | ||||||
|  | using System.Collections.Concurrent; | ||||||
|  | using System.Runtime.CompilerServices; | ||||||
|  | using System.Threading.Tasks.Sources; | ||||||
|  |  | ||||||
|  | using TouchSocket.Core; | ||||||
|  |  | ||||||
|  | namespace BenchmarkConsoleApp; | ||||||
|  |  | ||||||
|  |  | ||||||
|  | [MemoryDiagnoser] | ||||||
|  | public class BenchmarkAsyncWaitData | ||||||
|  | { | ||||||
|  |     private int Count = 100000; | ||||||
|  |  | ||||||
|  |     [Benchmark] | ||||||
|  |     public async Task RunAsyncWaitDataPool() | ||||||
|  |     { | ||||||
|  |         var waitHandlePool = new WaitHandlePool<MyWaitData>(); | ||||||
|  |         var cts = new CancellationTokenSource(1000 * 60); | ||||||
|  |         for (var i = 0; i < this.Count; i++) | ||||||
|  |         { | ||||||
|  |             var data = new MyWaitData(); | ||||||
|  |             using (var waitData = waitHandlePool.GetWaitDataAsync(data)) | ||||||
|  |             { | ||||||
|  |                 var task = Task.Run(() => | ||||||
|  |                 { | ||||||
|  |                     waitHandlePool.Set(data); | ||||||
|  |                 }); | ||||||
|  |  | ||||||
|  |  | ||||||
|  |                 await waitData.WaitAsync(cts.Token).ConfigureAwait(false); | ||||||
|  |  | ||||||
|  |                 await task; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     [Benchmark] | ||||||
|  |     public async Task RunAsyncWaitData() | ||||||
|  |     { | ||||||
|  |         var waitHandlePool = new WaitHandlePool2<MyWaitData>(); | ||||||
|  |         var cts = new CancellationTokenSource(1000 * 60); | ||||||
|  |         for (var i = 0; i < this.Count; i++) | ||||||
|  |         { | ||||||
|  |             var data = new MyWaitData(); | ||||||
|  |             using (var waitData = waitHandlePool.GetWaitDataAsync(data)) | ||||||
|  |             { | ||||||
|  |                 var task = Task.Run(() => | ||||||
|  |                 { | ||||||
|  |                     waitHandlePool.Set(data); | ||||||
|  |                 }); | ||||||
|  |  | ||||||
|  |  | ||||||
|  |                 await waitData.WaitAsync(cts.Token).ConfigureAwait(false); | ||||||
|  |  | ||||||
|  |                 await task; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     [Benchmark] | ||||||
|  |     public async Task RunAsyncWaitDataDelayPool() | ||||||
|  |     { | ||||||
|  |         var waitHandlePool = new WaitHandlePool<MyWaitData>(); | ||||||
|  |         var cts = new CancellationTokenSource(1000 * 60); | ||||||
|  |         for (var i = 0; i < this.Count; i++) | ||||||
|  |         { | ||||||
|  |             var data = new MyWaitData(); | ||||||
|  |             using (var waitData = waitHandlePool.GetWaitDataAsync(data)) | ||||||
|  |             { | ||||||
|  |                 var task = waitData.WaitAsync(cts.Token).ConfigureAwait(false); | ||||||
|  |  | ||||||
|  |                 waitData.Set(data); | ||||||
|  |  | ||||||
|  |                 await task; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     [Benchmark] | ||||||
|  |     public async Task RunAsyncWaitDataDelay() | ||||||
|  |     { | ||||||
|  |         var waitHandlePool = new WaitHandlePool2<MyWaitData>(); | ||||||
|  |         var cts = new CancellationTokenSource(1000 * 60); | ||||||
|  |         for (var i = 0; i < this.Count; i++) | ||||||
|  |         { | ||||||
|  |             var data = new MyWaitData(); | ||||||
|  |             using (var waitData = waitHandlePool.GetWaitDataAsync(data)) | ||||||
|  |             { | ||||||
|  |                 var task = waitData.WaitAsync(cts.Token).ConfigureAwait(false); | ||||||
|  |  | ||||||
|  |                 waitData.Set(data); | ||||||
|  |  | ||||||
|  |                 await task; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private class MyWaitData : IWaitHandle | ||||||
|  |     { | ||||||
|  |         public int Sign { get; set; } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public sealed class WaitHandlePool2<T> | ||||||
|  |     where T : class, IWaitHandle | ||||||
|  |     { | ||||||
|  |         private readonly int m_maxSign; | ||||||
|  |         private readonly int m_minSign; | ||||||
|  |         private readonly ConcurrentDictionary<int, AsyncWaitData2<T>> m_waitDic = new(); | ||||||
|  |         private readonly Action<int> _remove; | ||||||
|  |         private int m_currentSign; | ||||||
|  |  | ||||||
|  |         /// <summary> | ||||||
|  |         /// 初始化<see cref="WaitHandlePool{T}"/>类的新实例。 | ||||||
|  |         /// </summary> | ||||||
|  |         /// <param name="minSign">签名的最小值,默认为1。</param> | ||||||
|  |         /// <param name="maxSign">签名的最大值,默认为<see cref="int.MaxValue"/>。</param> | ||||||
|  |         /// <remarks> | ||||||
|  |         /// 签名范围用于控制自动生成的唯一标识符的取值范围。 | ||||||
|  |         /// 当签名达到最大值时,会自动重置到最小值重新开始分配。 | ||||||
|  |         /// </remarks> | ||||||
|  |         public WaitHandlePool2(int minSign = 1, int maxSign = int.MaxValue) | ||||||
|  |         { | ||||||
|  |             this.m_minSign = minSign; | ||||||
|  |             this.m_currentSign = minSign; | ||||||
|  |             this.m_maxSign = maxSign; | ||||||
|  |  | ||||||
|  |             this._remove = this.Remove; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         /// <summary> | ||||||
|  |         /// 取消池中所有等待操作。 | ||||||
|  |         /// </summary> | ||||||
|  |         /// <remarks> | ||||||
|  |         /// 此方法会遍历池中所有的等待数据,并调用其<see cref="AsyncWaitData{T}.Cancel"/>方法来取消等待。 | ||||||
|  |         /// 取消后的等待数据会从池中移除。适用于应用程序关闭或需要批量取消所有等待操作的场景。 | ||||||
|  |         /// </remarks> | ||||||
|  |         public void CancelAll() | ||||||
|  |         { | ||||||
|  |             var signs = this.m_waitDic.Keys.ToList(); | ||||||
|  |             foreach (var sign in signs) | ||||||
|  |             { | ||||||
|  |                 if (this.m_waitDic.TryRemove(sign, out var item)) | ||||||
|  |                 { | ||||||
|  |                     item.Cancel(); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         /// <summary> | ||||||
|  |         /// 获取与指定结果关联的异步等待数据。 | ||||||
|  |         /// </summary> | ||||||
|  |         /// <param name="result">要关联的结果对象。</param> | ||||||
|  |         /// <param name="autoSign">指示是否自动为结果对象分配签名,默认为<see langword="true"/>。</param> | ||||||
|  |         /// <returns>创建的<see cref="AsyncWaitData{T}"/>实例。</returns> | ||||||
|  |         /// <exception cref="InvalidOperationException">当指定的签名已被使用时抛出。</exception> | ||||||
|  |         /// <remarks> | ||||||
|  |         /// 如果<paramref name="autoSign"/>为<see langword="true"/>,方法会自动为结果对象生成唯一签名。 | ||||||
|  |         /// 创建的等待数据会被添加到池中,直到被设置结果或取消时才会移除。 | ||||||
|  |         /// </remarks> | ||||||
|  |         public AsyncWaitData2<T> GetWaitDataAsync(T result, bool autoSign = true) | ||||||
|  |         { | ||||||
|  |             if (autoSign) | ||||||
|  |             { | ||||||
|  |                 result.Sign = this.GetSign(); | ||||||
|  |             } | ||||||
|  |             var waitDataAsyncSlim = new AsyncWaitData2<T>(result.Sign, this._remove, result); | ||||||
|  |  | ||||||
|  |             if (!this.m_waitDic.TryAdd(result.Sign, waitDataAsyncSlim)) | ||||||
|  |             { | ||||||
|  |                 //ThrowHelper.ThrowInvalidOperationException($"The sign '{result.Sign}' is already in use."); | ||||||
|  |                 return default; | ||||||
|  |             } | ||||||
|  |             return waitDataAsyncSlim; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         /// <summary> | ||||||
|  |         /// 获取具有自动生成签名的异步等待数据。 | ||||||
|  |         /// </summary> | ||||||
|  |         /// <param name="sign">输出参数,返回自动生成的签名值。</param> | ||||||
|  |         /// <returns>创建的<see cref="AsyncWaitData{T}"/>实例。</returns> | ||||||
|  |         /// <exception cref="InvalidOperationException">当生成的签名已被使用时抛出。</exception> | ||||||
|  |         /// <remarks> | ||||||
|  |         /// 此方法会自动生成唯一签名,并创建不包含挂起数据的等待对象。 | ||||||
|  |         /// 适用于只需要等待通知而不关心具体数据内容的场景。 | ||||||
|  |         /// </remarks> | ||||||
|  |         public AsyncWaitData2<T> GetWaitDataAsync(out int sign) | ||||||
|  |         { | ||||||
|  |             sign = this.GetSign(); | ||||||
|  |             var waitDataAsyncSlim = new AsyncWaitData2<T>(sign, this._remove, default); | ||||||
|  |             if (!this.m_waitDic.TryAdd(sign, waitDataAsyncSlim)) | ||||||
|  |             { | ||||||
|  |                 return default; | ||||||
|  |             } | ||||||
|  |             return waitDataAsyncSlim; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         /// <summary> | ||||||
|  |         /// 使用指定结果设置对应签名的等待操作。 | ||||||
|  |         /// </summary> | ||||||
|  |         /// <param name="result">包含签名和结果数据的对象。</param> | ||||||
|  |         /// <returns>如果成功设置等待操作则返回<see langword="true"/>;否则返回<see langword="false"/>。</returns> | ||||||
|  |         /// <remarks> | ||||||
|  |         /// 此方法根据结果对象的签名查找对应的等待数据,并设置其结果。 | ||||||
|  |         /// 设置成功后,等待数据会从池中移除,正在等待的任务会被完成。 | ||||||
|  |         /// 如果找不到对应签名的等待数据,则返回<see langword="false"/>。 | ||||||
|  |         /// </remarks> | ||||||
|  |         public bool Set(T result) | ||||||
|  |         { | ||||||
|  |             if (this.m_waitDic.TryRemove(result.Sign, out var waitDataAsync)) | ||||||
|  |             { | ||||||
|  |                 waitDataAsync.Set(result); | ||||||
|  |                 return true; | ||||||
|  |             } | ||||||
|  |             return false; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         /// <summary> | ||||||
|  |         /// 尝试获取指定签名的异步等待数据。 | ||||||
|  |         /// </summary> | ||||||
|  |         /// <param name="sign">要查找的签名。</param> | ||||||
|  |         /// <param name="waitDataAsync">输出参数,如果找到则返回对应的等待数据;否则为<see langword="null"/>。</param> | ||||||
|  |         /// <returns>如果找到指定签名的等待数据则返回<see langword="true"/>;否则返回<see langword="false"/>。</returns> | ||||||
|  |         /// <remarks> | ||||||
|  |         /// 此方法允许查询池中是否存在特定签名的等待数据,而不会修改池的状态。 | ||||||
|  |         /// 适用于需要检查等待状态或获取等待数据进行进一步操作的场景。 | ||||||
|  |         /// </remarks> | ||||||
|  |         public bool TryGetDataAsync(int sign, out AsyncWaitData2<T> waitDataAsync) | ||||||
|  |         { | ||||||
|  |             return this.m_waitDic.TryGetValue(sign, out waitDataAsync); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         /// <summary> | ||||||
|  |         /// 生成下一个可用的唯一签名。 | ||||||
|  |         /// </summary> | ||||||
|  |         /// <returns>生成的唯一签名值。</returns> | ||||||
|  |         /// <remarks> | ||||||
|  |         /// 使用原子递增操作确保签名的唯一性和线程安全性。 | ||||||
|  |         /// 当签名达到最大值时,会重新开始分配以避免溢出。 | ||||||
|  |         /// </remarks> | ||||||
|  |         [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||||
|  |         private int GetSign() | ||||||
|  |         { | ||||||
|  |             while (true) | ||||||
|  |             { | ||||||
|  |                 var currentSign = this.m_currentSign; | ||||||
|  |                 var nextSign = currentSign >= this.m_maxSign ? this.m_minSign : currentSign + 1; | ||||||
|  |  | ||||||
|  |                 if (Interlocked.CompareExchange(ref this.m_currentSign, nextSign, currentSign) == currentSign) | ||||||
|  |                 { | ||||||
|  |                     return nextSign; | ||||||
|  |                 } | ||||||
|  |                 // 如果CAS失败,继续重试 | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         /// <summary> | ||||||
|  |         /// 从池中移除指定签名的等待数据。 | ||||||
|  |         /// </summary> | ||||||
|  |         /// <param name="sign">要移除的签名。</param> | ||||||
|  |         /// <remarks> | ||||||
|  |         /// 此方法由等待数据在释放时自动调用,确保池中不会保留已完成或已取消的等待对象。 | ||||||
|  |         /// </remarks> | ||||||
|  |         private void Remove(int sign) | ||||||
|  |         { | ||||||
|  |             this.m_waitDic.TryRemove(sign, out _); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public sealed class AsyncWaitData2<T> : DisposableObject, IValueTaskSource<WaitDataStatus> | ||||||
|  |     { | ||||||
|  |         // ManualResetValueTaskSourceCore 是一个结构体,避免了额外托管对象分配,但需要配合 token 使用。 | ||||||
|  |         private ManualResetValueTaskSourceCore<T> _core; // 核心结构体,不会分配额外对象 | ||||||
|  |  | ||||||
|  |         // 缓存的移除回调,由 WaitHandlePool 构造时传入,避免每次分配委托。 | ||||||
|  |         private readonly Action<int> _remove; | ||||||
|  |  | ||||||
|  |         // 挂起时的临时数据 | ||||||
|  |         private readonly T _pendingData; | ||||||
|  |  | ||||||
|  |         // 完成时的数据 | ||||||
|  |         private T _completedData; | ||||||
|  |  | ||||||
|  |         // 当前等待状态(成功/取消/未完成等) | ||||||
|  |         private WaitDataStatus _status; | ||||||
|  |         private CancellationTokenRegistration Registration; | ||||||
|  |  | ||||||
|  |         /// <summary> | ||||||
|  |         /// 使用指定签名和移除回调初始化一个新的 <see cref="AsyncWaitData{T}"/> 实例。 | ||||||
|  |         /// </summary> | ||||||
|  |         /// <param name="sign">此等待项对应的签名(用于在池中查找)。</param> | ||||||
|  |         /// <param name="remove">完成或释放时调用的回调,用于将此实例从等待池中移除。</param> | ||||||
|  |         /// <param name="pendingData">可选的挂起数据,当创建时可以携带一个初始占位数据。</param> | ||||||
|  |         public AsyncWaitData2(int sign, Action<int> remove, T pendingData) | ||||||
|  |         { | ||||||
|  |             this.Sign = sign; | ||||||
|  |             this._remove = remove; | ||||||
|  |             this._pendingData = pendingData; | ||||||
|  |             this._core.RunContinuationsAsynchronously = true; // 确保续体异步执行,避免潜在的栈内联执行问题 | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         /// <summary> | ||||||
|  |         /// 获取此等待项的签名标识。 | ||||||
|  |         /// </summary> | ||||||
|  |         public int Sign { get; } | ||||||
|  |  | ||||||
|  |         /// <summary> | ||||||
|  |         /// 获取挂起时的原始数据(如果在创建时传入)。 | ||||||
|  |         /// </summary> | ||||||
|  |         public T PendingData => this._pendingData; | ||||||
|  |  | ||||||
|  |         /// <summary> | ||||||
|  |         /// 获取已完成时的返回数据。 | ||||||
|  |         /// </summary> | ||||||
|  |         public T CompletedData => this._completedData; | ||||||
|  |  | ||||||
|  |         /// <summary> | ||||||
|  |         /// 获取当前等待状态(例如:Success、Canceled 等)。 | ||||||
|  |         /// </summary> | ||||||
|  |         public WaitDataStatus Status => this._status; | ||||||
|  |  | ||||||
|  |         /// <summary> | ||||||
|  |         /// 取消当前等待,标记为已取消并触发等待任务的异常(OperationCanceledException)。 | ||||||
|  |         /// </summary> | ||||||
|  |         public void Cancel() | ||||||
|  |         { | ||||||
|  |             this.Set(WaitDataStatus.Canceled, default!); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         /// <summary> | ||||||
|  |         /// 将等待项设置为成功并携带结果数据。 | ||||||
|  |         /// </summary> | ||||||
|  |         /// <param name="result">要设置的完成数据。</param> | ||||||
|  |         public void Set(T result) | ||||||
|  |         { | ||||||
|  |             this.Set(WaitDataStatus.Success, result); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         /// <summary> | ||||||
|  |         /// 设置等待项的状态和数据,并完成对应的 ValueTask。 | ||||||
|  |         /// </summary> | ||||||
|  |         /// <param name="status">要设置的状态。</param> | ||||||
|  |         /// <param name="result">要设置的完成数据。</param> | ||||||
|  |         public void Set(WaitDataStatus status, T result) | ||||||
|  |         { | ||||||
|  |             this._status = status; | ||||||
|  |             this._completedData = result; | ||||||
|  |  | ||||||
|  |             if (status == WaitDataStatus.Canceled) | ||||||
|  |                 this._core.SetException(new OperationCanceledException()); | ||||||
|  |             else | ||||||
|  |                 this._core.SetResult(result); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         /// <summary> | ||||||
|  |         /// 异步等待此项完成,返回一个 <see cref="ValueTask{WaitDataStatus}"/>,可传入取消令牌以取消等待。 | ||||||
|  |         /// </summary> | ||||||
|  |         /// <param name="cancellationToken">可选的取消令牌。若触发则会调用 <see cref="Cancel"/>。</param> | ||||||
|  |         /// <returns>表示等待状态的 ValueTask。</returns> | ||||||
|  |         public ValueTask<WaitDataStatus> WaitAsync(CancellationToken cancellationToken = default) | ||||||
|  |         { | ||||||
|  |             if (cancellationToken.CanBeCanceled) | ||||||
|  |             { | ||||||
|  |                 this.Registration = cancellationToken.Register(this.Cancel); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             return new ValueTask<WaitDataStatus>(this, this._core.Version); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         /// <summary> | ||||||
|  |         /// 从核心获取结果(显式接口实现)。 | ||||||
|  |         /// 注意:此方法由 ValueTask 基础设施调用,不应直接在用户代码中调用。 | ||||||
|  |         /// </summary> | ||||||
|  |         WaitDataStatus IValueTaskSource<WaitDataStatus>.GetResult(short token) | ||||||
|  |         { | ||||||
|  |             this._core.GetResult(token); | ||||||
|  |             return this._status; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         /// <summary> | ||||||
|  |         /// 获取当前 ValueTask 源的状态(显式接口实现)。 | ||||||
|  |         /// </summary> | ||||||
|  |         ValueTaskSourceStatus IValueTaskSource<WaitDataStatus>.GetStatus(short token) | ||||||
|  |             => this._core.GetStatus(token); | ||||||
|  |  | ||||||
|  |         /// <summary> | ||||||
|  |         /// 注册续体(显式接口实现)。 | ||||||
|  |         /// 注意:flags 可以控制是否捕获上下文等行为。 | ||||||
|  |         /// </summary> | ||||||
|  |         void IValueTaskSource<WaitDataStatus>.OnCompleted(Action<object?> continuation, object? state, | ||||||
|  |             short token, ValueTaskSourceOnCompletedFlags flags) | ||||||
|  |             => this._core.OnCompleted(continuation, state, token, flags); | ||||||
|  |  | ||||||
|  |         /// <summary> | ||||||
|  |         /// 释放托管资源时调用,会触发传入的移除回调,从所在的等待池中移除此等待项。 | ||||||
|  |         /// </summary> | ||||||
|  |         /// <param name="disposing">是否为显式释放。</param> | ||||||
|  |         protected override void Dispose(bool disposing) | ||||||
|  |         { | ||||||
|  |             if (disposing) | ||||||
|  |             { | ||||||
|  |                 this.Registration.Dispose(); | ||||||
|  |                 this._remove(this.Sign); | ||||||
|  |             } | ||||||
|  |             base.Dispose(disposing); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  | } | ||||||
| @@ -14,8 +14,6 @@ | |||||||
| using BenchmarkDotNet.Configs; | using BenchmarkDotNet.Configs; | ||||||
| using BenchmarkDotNet.Running; | using BenchmarkDotNet.Running; | ||||||
|  |  | ||||||
| using ThingsGateway.Foundation; |  | ||||||
|  |  | ||||||
| namespace BenchmarkConsoleApp | namespace BenchmarkConsoleApp | ||||||
| { | { | ||||||
|     internal class Program |     internal class Program | ||||||
| @@ -47,15 +45,18 @@ namespace BenchmarkConsoleApp | |||||||
|             //ManualConfig.Create(DefaultConfig.Instance) |             //ManualConfig.Create(DefaultConfig.Instance) | ||||||
|             //.WithOptions(ConfigOptions.DisableOptimizationsValidator) |             //.WithOptions(ConfigOptions.DisableOptimizationsValidator) | ||||||
|             //); |             //); | ||||||
|  |             BenchmarkRunner.Run<BenchmarkAsyncWaitData>( | ||||||
|             BenchmarkRunner.Run<SemaphoreBenchmark>( | ManualConfig.Create(DefaultConfig.Instance) | ||||||
|        ManualConfig.Create(DefaultConfig.Instance) | .WithOptions(ConfigOptions.DisableOptimizationsValidator) | ||||||
|            .WithOptions(ConfigOptions.DisableOptimizationsValidator) | ); | ||||||
|    ); |             //         BenchmarkRunner.Run<SemaphoreBenchmark>( | ||||||
| //            BenchmarkRunner.Run<ModbusBenchmark>( |             //    ManualConfig.Create(DefaultConfig.Instance) | ||||||
| //ManualConfig.Create(DefaultConfig.Instance) |             //        .WithOptions(ConfigOptions.DisableOptimizationsValidator) | ||||||
| //.WithOptions(ConfigOptions.DisableOptimizationsValidator) |             //); | ||||||
| //); |             //            BenchmarkRunner.Run<ModbusBenchmark>( | ||||||
|  |             //ManualConfig.Create(DefaultConfig.Instance) | ||||||
|  |             //.WithOptions(ConfigOptions.DisableOptimizationsValidator) | ||||||
|  |             //); | ||||||
|             //            BenchmarkRunner.Run<S7Benchmark>( |             //            BenchmarkRunner.Run<S7Benchmark>( | ||||||
|             //ManualConfig.Create(DefaultConfig.Instance) |             //ManualConfig.Create(DefaultConfig.Instance) | ||||||
|             //.WithOptions(ConfigOptions.DisableOptimizationsValidator) |             //.WithOptions(ConfigOptions.DisableOptimizationsValidator) | ||||||
|   | |||||||
| @@ -8,6 +8,8 @@ | |||||||
| //  QQ群:605534569 | //  QQ群:605534569 | ||||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||||
|  |  | ||||||
|  | using PooledAwait; | ||||||
|  |  | ||||||
| using System.Buffers; | using System.Buffers; | ||||||
| using System.Collections.Concurrent; | using System.Collections.Concurrent; | ||||||
| using System.Runtime.InteropServices; | using System.Runtime.InteropServices; | ||||||
| @@ -352,10 +354,10 @@ public class ModbusSlave : DeviceBase, IModbusAddress | |||||||
|                 Init(mAddress); |                 Init(mAddress); | ||||||
|             } |             } | ||||||
|  |  | ||||||
|             ModbusServer01ByteBlocks.TryGetValue(mAddress.Station,out var ModbusServer01ByteBlock); |             ModbusServer01ByteBlocks.TryGetValue(mAddress.Station, out var ModbusServer01ByteBlock); | ||||||
|             ModbusServer02ByteBlocks.TryGetValue(mAddress.Station,out var ModbusServer02ByteBlock); |             ModbusServer02ByteBlocks.TryGetValue(mAddress.Station, out var ModbusServer02ByteBlock); | ||||||
|             ModbusServer03ByteBlocks.TryGetValue(mAddress.Station,out var ModbusServer03ByteBlock); |             ModbusServer03ByteBlocks.TryGetValue(mAddress.Station, out var ModbusServer03ByteBlock); | ||||||
|             ModbusServer04ByteBlocks.TryGetValue(mAddress.Station,out var ModbusServer04ByteBlock); |             ModbusServer04ByteBlocks.TryGetValue(mAddress.Station, out var ModbusServer04ByteBlock); | ||||||
|             if (read) |             if (read) | ||||||
|             { |             { | ||||||
|                 using (new ReadLock(_lockSlim)) |                 using (new ReadLock(_lockSlim)) | ||||||
| @@ -475,33 +477,31 @@ public class ModbusSlave : DeviceBase, IModbusAddress | |||||||
|         return mAddress; |         return mAddress; | ||||||
|     } |     } | ||||||
|     /// <inheritdoc/> |     /// <inheritdoc/> | ||||||
|     public override async ValueTask<OperResult> WriteAsync(string address, ReadOnlyMemory<byte> value, DataTypeEnum dataType, CancellationToken cancellationToken = default) |     public override ValueTask<OperResult> WriteAsync(string address, ReadOnlyMemory<byte> value, DataTypeEnum dataType, CancellationToken cancellationToken = default) | ||||||
|     { |     { | ||||||
|         try |         try | ||||||
|         { |         { | ||||||
|             await EasyValueTask.CompletedTask.ConfigureAwait(false); |  | ||||||
|             var mAddress = GetModbusAddress(address, Station); |             var mAddress = GetModbusAddress(address, Station); | ||||||
|             mAddress.SlaveWriteDatas = new(value); |             mAddress.SlaveWriteDatas = new(value); | ||||||
|             return ModbusRequest(mAddress, false, cancellationToken); |             return EasyValueTask.FromResult<OperResult>(ModbusRequest(mAddress, false, cancellationToken)); | ||||||
|         } |         } | ||||||
|         catch (Exception ex) |         catch (Exception ex) | ||||||
|         { |         { | ||||||
|             return new OperResult(ex); |             return EasyValueTask.FromResult(new OperResult(ex)); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /// <inheritdoc/> |     /// <inheritdoc/> | ||||||
|     public override async ValueTask<OperResult> WriteAsync(string address, ReadOnlyMemory<bool> value, CancellationToken cancellationToken = default) |     public override ValueTask<OperResult> WriteAsync(string address, ReadOnlyMemory<bool> value, CancellationToken cancellationToken = default) | ||||||
|     { |     { | ||||||
|         try |         try | ||||||
|         { |         { | ||||||
|             await EasyValueTask.CompletedTask.ConfigureAwait(false); |  | ||||||
|             var mAddress = GetModbusAddress(address, Station); |             var mAddress = GetModbusAddress(address, Station); | ||||||
|             if (mAddress.IsBitFunction) |             if (mAddress.IsBitFunction) | ||||||
|             { |             { | ||||||
|                 mAddress.SlaveWriteDatas = new(value.Span.BoolToByte()); |                 mAddress.SlaveWriteDatas = new(value.Span.BoolToByte()); | ||||||
|                 ModbusRequest(mAddress, false, cancellationToken); |                 ModbusRequest(mAddress, false, cancellationToken); | ||||||
|                 return OperResult.Success; |                 return EasyValueTask.FromResult(OperResult.Success); | ||||||
|             } |             } | ||||||
|             else |             else | ||||||
|             { |             { | ||||||
| @@ -509,7 +509,7 @@ public class ModbusSlave : DeviceBase, IModbusAddress | |||||||
|                 { |                 { | ||||||
|                     mAddress.Length = 2; |                     mAddress.Length = 2; | ||||||
|                     var readData = ModbusRequest(mAddress, true, cancellationToken); |                     var readData = ModbusRequest(mAddress, true, cancellationToken); | ||||||
|                     if (!readData.IsSuccess) return readData; |                     if (!readData.IsSuccess) return EasyValueTask.FromResult<OperResult>(readData); | ||||||
|                     var writeData = TouchSocketBitConverter.BigEndian.To<ushort>(readData.Content.Span); |                     var writeData = TouchSocketBitConverter.BigEndian.To<ushort>(readData.Content.Span); | ||||||
|                     var span = value.Span; |                     var span = value.Span; | ||||||
|                     for (int i = 0; i < value.Length; i++) |                     for (int i = 0; i < value.Length; i++) | ||||||
| @@ -518,17 +518,17 @@ public class ModbusSlave : DeviceBase, IModbusAddress | |||||||
|                     } |                     } | ||||||
|                     mAddress.SlaveWriteDatas = new(ThingsGatewayBitConverter.GetBytes(writeData)); |                     mAddress.SlaveWriteDatas = new(ThingsGatewayBitConverter.GetBytes(writeData)); | ||||||
|                     ModbusRequest(mAddress, false, cancellationToken); |                     ModbusRequest(mAddress, false, cancellationToken); | ||||||
|                     return OperResult.Success; |                     return EasyValueTask.FromResult<OperResult>(OperResult.Success); | ||||||
|                 } |                 } | ||||||
|                 else |                 else | ||||||
|                 { |                 { | ||||||
|                     return new OperResult(string.Format(AppResource.ValueOverlimit, nameof(mAddress.BitIndex), 16)); |                     return EasyValueTask.FromResult<OperResult>(new OperResult(string.Format(AppResource.ValueOverlimit, nameof(mAddress.BitIndex), 16))); | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|         catch (Exception ex) |         catch (Exception ex) | ||||||
|         { |         { | ||||||
|             return new OperResult(ex); |             return EasyValueTask.FromResult<OperResult>(new OperResult(ex)); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|     protected override ValueTask ChannelReceived(IClientChannel client, ReceivedDataEventArgs e, bool last) |     protected override ValueTask ChannelReceived(IClientChannel client, ReceivedDataEventArgs e, bool last) | ||||||
| @@ -536,20 +536,26 @@ public class ModbusSlave : DeviceBase, IModbusAddress | |||||||
|         return HandleChannelReceivedAsync(client, e, last); |         return HandleChannelReceivedAsync(client, e, last); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private async ValueTask HandleChannelReceivedAsync(IClientChannel client, ReceivedDataEventArgs e, bool last) |     private ValueTask HandleChannelReceivedAsync(IClientChannel client, ReceivedDataEventArgs e, bool last) | ||||||
|     { |     { | ||||||
|         if (!TryParseRequest(e.RequestInfo, out var modbusRequest, out var sequences, out var modbusRtu)) |         return HandleChannelReceivedAsync(this, client, e); | ||||||
|  |  | ||||||
|  |         static async PooledValueTask HandleChannelReceivedAsync(ModbusSlave @this, IClientChannel client, ReceivedDataEventArgs e) | ||||||
|  |         { | ||||||
|  |             if (!TryParseRequest(e.RequestInfo, out var modbusRequest, out var sequences, out var modbusRtu)) | ||||||
|  |                 return; | ||||||
|  |  | ||||||
|  |             if (!@this.MulStation && modbusRequest.Station != @this.Station) | ||||||
|  |                 return; | ||||||
|  |  | ||||||
|  |             var function = NormalizeFunctionCode(modbusRequest.FunctionCode); | ||||||
|  |  | ||||||
|  |             if (function <= 4) | ||||||
|  |                 await @this.HandleReadRequestAsync(client, e, modbusRequest, sequences, modbusRtu).ConfigureAwait(false); | ||||||
|  |             else | ||||||
|  |                 await @this.HandleWriteRequestAsync(client, e, modbusRequest, sequences, modbusRtu, function).ConfigureAwait(false); | ||||||
|             return; |             return; | ||||||
|  |         } | ||||||
|         if (!MulStation && modbusRequest.Station != Station) |  | ||||||
|             return; |  | ||||||
|  |  | ||||||
|         var function = NormalizeFunctionCode(modbusRequest.FunctionCode); |  | ||||||
|  |  | ||||||
|         if (function <= 4) |  | ||||||
|             await HandleReadRequestAsync(client, e, modbusRequest, sequences, modbusRtu).ConfigureAwait(false); |  | ||||||
|         else |  | ||||||
|             await HandleWriteRequestAsync(client, e, modbusRequest, sequences, modbusRtu, function).ConfigureAwait(false); |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private static bool TryParseRequest(object requestInfo, out ModbusRequest modbusRequest, out ReadOnlySequence<byte> sequences, out bool modbusRtu) |     private static bool TryParseRequest(object requestInfo, out ModbusRequest modbusRequest, out ReadOnlySequence<byte> sequences, out bool modbusRtu) | ||||||
| @@ -580,7 +586,7 @@ public class ModbusSlave : DeviceBase, IModbusAddress | |||||||
|     private static byte NormalizeFunctionCode(byte funcCode) |     private static byte NormalizeFunctionCode(byte funcCode) | ||||||
|         => funcCode > 0x30 ? (byte)(funcCode - 0x30) : funcCode; |         => funcCode > 0x30 ? (byte)(funcCode - 0x30) : funcCode; | ||||||
|  |  | ||||||
|     private async Task HandleReadRequestAsync( |     private Task HandleReadRequestAsync( | ||||||
|         IClientChannel client, |         IClientChannel client, | ||||||
|         ReceivedDataEventArgs e, |         ReceivedDataEventArgs e, | ||||||
|         ModbusRequest modbusRequest, |         ModbusRequest modbusRequest, | ||||||
| @@ -590,27 +596,31 @@ public class ModbusSlave : DeviceBase, IModbusAddress | |||||||
|         var data = ModbusRequest(modbusRequest, true); |         var data = ModbusRequest(modbusRequest, true); | ||||||
|         if (!data.IsSuccess) |         if (!data.IsSuccess) | ||||||
|         { |         { | ||||||
|             await WriteError(modbusRtu, client, sequences, e).ConfigureAwait(false); |             return WriteError(modbusRtu, client, sequences, e); | ||||||
|             return; |  | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         ValueByteBlock byteBlock = new(1024); |         return Write(this, client, e, modbusRequest, sequences, modbusRtu, data); | ||||||
|         try |  | ||||||
|  |         static async PooledTask Write(ModbusSlave @this, IClientChannel client, ReceivedDataEventArgs e, ModbusRequest modbusRequest, ReadOnlySequence<byte> sequences, bool modbusRtu, OperResult<ReadOnlyMemory<byte>> data) | ||||||
|         { |         { | ||||||
|             WriteReadResponse(modbusRequest, sequences, data.Content, ref byteBlock, modbusRtu); |             ValueByteBlock byteBlock = new(1024); | ||||||
|             await ReturnData(client, byteBlock.Memory, e).ConfigureAwait(false); |             try | ||||||
|         } |             { | ||||||
|         catch |                 WriteReadResponse(modbusRequest, sequences, data.Content, ref byteBlock, modbusRtu); | ||||||
|         { |                 await @this.ReturnData(client, byteBlock.Memory, e).ConfigureAwait(false); | ||||||
|             await WriteError(modbusRtu, client, sequences, e).ConfigureAwait(false); |             } | ||||||
|         } |             catch | ||||||
|         finally |             { | ||||||
|         { |                 await @this.WriteError(modbusRtu, client, sequences, e).ConfigureAwait(false); | ||||||
|             byteBlock.SafeDispose(); |             } | ||||||
|  |             finally | ||||||
|  |             { | ||||||
|  |                 byteBlock.SafeDispose(); | ||||||
|  |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private async Task HandleWriteRequestAsync( |     private Task HandleWriteRequestAsync( | ||||||
|         IClientChannel client, |         IClientChannel client, | ||||||
|         ReceivedDataEventArgs e, |         ReceivedDataEventArgs e, | ||||||
|         ModbusRequest modbusRequest, |         ModbusRequest modbusRequest, | ||||||
| @@ -618,50 +628,62 @@ public class ModbusSlave : DeviceBase, IModbusAddress | |||||||
|         bool modbusRtu, |         bool modbusRtu, | ||||||
|         byte f) |         byte f) | ||||||
|     { |     { | ||||||
|         var modbusAddress = new ModbusAddress(modbusRequest); |         return HandleWriteRequestAsync(this, client, e, modbusRequest, sequences, modbusRtu, f); | ||||||
|         bool isSuccess; |  | ||||||
|  |  | ||||||
|         switch (f) |  | ||||||
|  |         static async PooledTask HandleWriteRequestAsync(ModbusSlave @this, IClientChannel client, ReceivedDataEventArgs e, ModbusRequest modbusRequest, ReadOnlySequence<byte> sequences, bool modbusRtu, byte f) | ||||||
|         { |         { | ||||||
|             case 5: |             var modbusAddress = new ModbusAddress(modbusRequest); | ||||||
|             case 15: |             bool isSuccess; | ||||||
|                 modbusAddress.WriteFunctionCode = modbusRequest.FunctionCode; |  | ||||||
|                 modbusAddress.FunctionCode = 1; |  | ||||||
|                 isSuccess = await HandleWriteCoreAsync(modbusAddress, client, modbusRequest).ConfigureAwait(false); |  | ||||||
|                 break; |  | ||||||
|  |  | ||||||
|             case 6: |             switch (f) | ||||||
|             case 16: |             { | ||||||
|                 modbusAddress.WriteFunctionCode = modbusRequest.FunctionCode; |                 case 5: | ||||||
|                 modbusAddress.FunctionCode = 3; |                 case 15: | ||||||
|                 isSuccess = await HandleWriteCoreAsync(modbusAddress, client, modbusRequest).ConfigureAwait(false); |                     modbusAddress.WriteFunctionCode = modbusRequest.FunctionCode; | ||||||
|                 break; |                     modbusAddress.FunctionCode = 1; | ||||||
|  |                     isSuccess = await @this.HandleWriteCoreAsync(modbusAddress, client, modbusRequest).ConfigureAwait(false); | ||||||
|  |                     break; | ||||||
|  |  | ||||||
|             default: |                 case 6: | ||||||
|                 return; |                 case 16: | ||||||
|  |                     modbusAddress.WriteFunctionCode = modbusRequest.FunctionCode; | ||||||
|  |                     modbusAddress.FunctionCode = 3; | ||||||
|  |                     isSuccess = await @this.HandleWriteCoreAsync(modbusAddress, client, modbusRequest).ConfigureAwait(false); | ||||||
|  |                     break; | ||||||
|  |  | ||||||
|  |                 default: | ||||||
|  |                     return; | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             if (isSuccess) | ||||||
|  |                 await @this.WriteSuccess(modbusRtu, client, sequences, e).ConfigureAwait(false); | ||||||
|  |             else | ||||||
|  |                 await @this.WriteError(modbusRtu, client, sequences, e).ConfigureAwait(false); | ||||||
|  |             return; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         if (isSuccess) |  | ||||||
|             await WriteSuccess(modbusRtu, client, sequences, e).ConfigureAwait(false); |  | ||||||
|         else |  | ||||||
|             await WriteError(modbusRtu, client, sequences, e).ConfigureAwait(false); |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private async Task<bool> HandleWriteCoreAsync(ModbusAddress address, IClientChannel client, ModbusRequest modbusRequest) |     private Task<bool> HandleWriteCoreAsync(ModbusAddress address, IClientChannel client, ModbusRequest modbusRequest) | ||||||
|     { |     { | ||||||
|         if (WriteData != null) |         return HandleWriteCoreAsync(this, address, client, modbusRequest); | ||||||
|         { |  | ||||||
|             var result = await WriteData(address, ThingsGatewayBitConverter, client).ConfigureAwait(false); |  | ||||||
|             if (!result.IsSuccess) return false; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         if (IsWriteMemory) |         static async PooledTask<bool> HandleWriteCoreAsync(ModbusSlave @this, ModbusAddress address, IClientChannel client, ModbusRequest modbusRequest) | ||||||
|         { |         { | ||||||
|             var memResult = ModbusRequest(modbusRequest, false); |             if (@this.WriteData != null) | ||||||
|             return memResult.IsSuccess; |             { | ||||||
|         } |                 var result = await @this.WriteData(address, @this.ThingsGatewayBitConverter, client).ConfigureAwait(false); | ||||||
|  |                 if (!result.IsSuccess) return false; | ||||||
|  |             } | ||||||
|  |  | ||||||
|         return true; |             if (@this.IsWriteMemory) | ||||||
|  |             { | ||||||
|  |                 var memResult = @this.ModbusRequest(modbusRequest, false); | ||||||
|  |                 return memResult.IsSuccess; | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             return true; | ||||||
|  |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private static void WriteReadResponse( |     private static void WriteReadResponse( | ||||||
| @@ -696,63 +718,78 @@ public class ModbusSlave : DeviceBase, IModbusAddress | |||||||
|             ByteBlockExtension.WriteBackValue(ref byteBlock, (byte)(byteBlock.Length - 6), EndianType.Big, 5); |             ByteBlockExtension.WriteBackValue(ref byteBlock, (byte)(byteBlock.Length - 6), EndianType.Big, 5); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private async Task ReturnData(IClientChannel client, ReadOnlyMemory<byte> sendData, ReceivedDataEventArgs e) |     private Task ReturnData(IClientChannel client, ReadOnlyMemory<byte> sendData, ReceivedDataEventArgs e) | ||||||
|     { |     { | ||||||
|         if (SendDelayTime > 0) |         return ReturnData(SendDelayTime, client, sendData, e); | ||||||
|             await Task.Delay(SendDelayTime).ConfigureAwait(false); |  | ||||||
|         if (client is IUdpClientSender udpClientSender) |  | ||||||
|             await udpClientSender.SendAsync(((UdpReceivedDataEventArgs)e).EndPoint, sendData).ConfigureAwait(false); |  | ||||||
|         else |  | ||||||
|             await client.SendAsync(sendData, client.ClosedToken).ConfigureAwait(false); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private async Task WriteError(bool modbusRtu, IClientChannel client, ReadOnlySequence<byte> bytes, ReceivedDataEventArgs e) |         static async PooledTask ReturnData(int deley, IClientChannel client, ReadOnlyMemory<byte> sendData, ReceivedDataEventArgs e) | ||||||
|     { |  | ||||||
|         ValueByteBlock byteBlock = new(20); |  | ||||||
|         try |  | ||||||
|         { |         { | ||||||
|             if (modbusRtu) |             if (deley > 0) | ||||||
|             { |                 await Task.Delay(deley).ConfigureAwait(false); | ||||||
|                 ByteBlockExtension.Write(ref byteBlock, bytes.Slice(0, 2)); |             if (client is IUdpClientSender udpClientSender) | ||||||
|                 WriterExtension.WriteValue(ref byteBlock, (byte)1); |                 await udpClientSender.SendAsync(((UdpReceivedDataEventArgs)e).EndPoint, sendData).ConfigureAwait(false); | ||||||
|                 byteBlock.Write(CRC16Utils.Crc16Only(byteBlock.Span)); |  | ||||||
|                 ByteBlockExtension.WriteBackValue(ref byteBlock, (byte)(byteBlock.Span[1] + 128), EndianType.Big, 1); |  | ||||||
|             } |  | ||||||
|             else |             else | ||||||
|             { |                 await client.SendAsync(sendData, client.ClosedToken).ConfigureAwait(false); | ||||||
|                 ByteBlockExtension.Write(ref byteBlock, bytes.Slice(0, 8)); |  | ||||||
|                 WriterExtension.WriteValue(ref byteBlock, (byte)1); |  | ||||||
|                 ByteBlockExtension.WriteBackValue(ref byteBlock, (byte)(byteBlock.Length - 6), EndianType.Big, 5); |  | ||||||
|                 ByteBlockExtension.WriteBackValue(ref byteBlock, (byte)(byteBlock.Span[7] + 128), EndianType.Big, 7); |  | ||||||
|             } |  | ||||||
|             await ReturnData(client, byteBlock.Memory, e).ConfigureAwait(false); |  | ||||||
|         } |  | ||||||
|         finally |  | ||||||
|         { |  | ||||||
|             byteBlock.SafeDispose(); |  | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private async Task WriteSuccess(bool modbusRtu, IClientChannel client, ReadOnlySequence<byte> bytes, ReceivedDataEventArgs e) |     private Task WriteError(bool modbusRtu, IClientChannel client, ReadOnlySequence<byte> bytes, ReceivedDataEventArgs e) | ||||||
|     { |     { | ||||||
|         ValueByteBlock byteBlock = new(20); |         return WriteError(this, modbusRtu, client, bytes, e); | ||||||
|         try |  | ||||||
|  |         static async PooledTask WriteError(ModbusSlave @this, bool modbusRtu, IClientChannel client, ReadOnlySequence<byte> bytes, ReceivedDataEventArgs e) | ||||||
|         { |         { | ||||||
|             if (modbusRtu) |             ValueByteBlock byteBlock = new(20); | ||||||
|  |             try | ||||||
|             { |             { | ||||||
|                 ByteBlockExtension.Write(ref byteBlock, bytes.Slice(0, 6)); |                 if (modbusRtu) | ||||||
|                 byteBlock.Write(CRC16Utils.Crc16Only(byteBlock.Span)); |                 { | ||||||
|  |                     ByteBlockExtension.Write(ref byteBlock, bytes.Slice(0, 2)); | ||||||
|  |                     WriterExtension.WriteValue(ref byteBlock, (byte)1); | ||||||
|  |                     byteBlock.Write(CRC16Utils.Crc16Only(byteBlock.Span)); | ||||||
|  |                     ByteBlockExtension.WriteBackValue(ref byteBlock, (byte)(byteBlock.Span[1] + 128), EndianType.Big, 1); | ||||||
|  |                 } | ||||||
|  |                 else | ||||||
|  |                 { | ||||||
|  |                     ByteBlockExtension.Write(ref byteBlock, bytes.Slice(0, 8)); | ||||||
|  |                     WriterExtension.WriteValue(ref byteBlock, (byte)1); | ||||||
|  |                     ByteBlockExtension.WriteBackValue(ref byteBlock, (byte)(byteBlock.Length - 6), EndianType.Big, 5); | ||||||
|  |                     ByteBlockExtension.WriteBackValue(ref byteBlock, (byte)(byteBlock.Span[7] + 128), EndianType.Big, 7); | ||||||
|  |                 } | ||||||
|  |                 await @this.ReturnData(client, byteBlock.Memory, e).ConfigureAwait(false); | ||||||
|             } |             } | ||||||
|             else |             finally | ||||||
|             { |             { | ||||||
|                 ByteBlockExtension.Write(ref byteBlock, bytes.Slice(0, 12)); |                 byteBlock.SafeDispose(); | ||||||
|                 ByteBlockExtension.WriteBackValue(ref byteBlock, (byte)(byteBlock.Length - 6), EndianType.Big, 5); |  | ||||||
|             } |             } | ||||||
|             await ReturnData(client, byteBlock.Memory, e).ConfigureAwait(false); |  | ||||||
|         } |         } | ||||||
|         finally |     } | ||||||
|  |  | ||||||
|  |     private Task WriteSuccess(bool modbusRtu, IClientChannel client, ReadOnlySequence<byte> bytes, ReceivedDataEventArgs e) | ||||||
|  |     { | ||||||
|  |         return WriteSuccess(this, modbusRtu, client, bytes, e); | ||||||
|  |  | ||||||
|  |         static async PooledTask WriteSuccess(ModbusSlave @this, bool modbusRtu, IClientChannel client, ReadOnlySequence<byte> bytes, ReceivedDataEventArgs e) | ||||||
|         { |         { | ||||||
|             byteBlock.SafeDispose(); |             ValueByteBlock byteBlock = new(20); | ||||||
|  |             try | ||||||
|  |             { | ||||||
|  |                 if (modbusRtu) | ||||||
|  |                 { | ||||||
|  |                     ByteBlockExtension.Write(ref byteBlock, bytes.Slice(0, 6)); | ||||||
|  |                     byteBlock.Write(CRC16Utils.Crc16Only(byteBlock.Span)); | ||||||
|  |                 } | ||||||
|  |                 else | ||||||
|  |                 { | ||||||
|  |                     ByteBlockExtension.Write(ref byteBlock, bytes.Slice(0, 12)); | ||||||
|  |                     ByteBlockExtension.WriteBackValue(ref byteBlock, (byte)(byteBlock.Length - 6), EndianType.Big, 5); | ||||||
|  |                 } | ||||||
|  |                 await @this.ReturnData(client, byteBlock.Memory, e).ConfigureAwait(false); | ||||||
|  |             } | ||||||
|  |             finally | ||||||
|  |             { | ||||||
|  |                 byteBlock.SafeDispose(); | ||||||
|  |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -395,18 +395,17 @@ public class OpcUaMaster : IAsyncDisposable | |||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// 连接到服务器 |     /// 连接到服务器 | ||||||
|     /// </summary> |     /// </summary> | ||||||
|     public async Task ConnectAsync(CancellationToken cancellationToken) |     public Task ConnectAsync(CancellationToken cancellationToken) | ||||||
|     { |     { | ||||||
|         await ConnectAsync(OpcUaProperty.OpcUrl, cancellationToken).ConfigureAwait(false); |         return ConnectAsync(OpcUaProperty.OpcUrl, cancellationToken); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// 断开连接。 |     /// 断开连接。 | ||||||
|     /// </summary> |     /// </summary> | ||||||
|     public async Task DisconnectAsync() |     public Task DisconnectAsync() | ||||||
|     { |     { | ||||||
|         await PrivateDisconnectAsync().ConfigureAwait(false); |         return PrivateDisconnectAsync(); | ||||||
|  |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     public async ValueTask DisposeAsync() |     public async ValueTask DisposeAsync() | ||||||
|   | |||||||
| @@ -8,6 +8,8 @@ | |||||||
| //  QQ群:605534569 | //  QQ群:605534569 | ||||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||||
|  |  | ||||||
|  | using PooledAwait; | ||||||
|  |  | ||||||
| using ThingsGateway.Foundation.Extension.String; | using ThingsGateway.Foundation.Extension.String; | ||||||
| using ThingsGateway.NewLife; | using ThingsGateway.NewLife; | ||||||
| using ThingsGateway.NewLife.Extension; | using ThingsGateway.NewLife.Extension; | ||||||
| @@ -144,71 +146,76 @@ public partial class SiemensS7Master : DeviceBase | |||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// 此方法并不会智能分组以最大化效率,减少传输次数,因为返回值是byte[],所以一切都按地址数组的顺序执行,最后合并数组 |     /// 此方法并不会智能分组以最大化效率,减少传输次数,因为返回值是byte[],所以一切都按地址数组的顺序执行,最后合并数组 | ||||||
|     /// </summary> |     /// </summary> | ||||||
|     public async ValueTask<OperResult<ReadOnlyMemory<byte>>> S7ReadAsync( |     public ValueTask<OperResult<ReadOnlyMemory<byte>>> S7ReadAsync( | ||||||
|         SiemensS7Address[] addresses, |         SiemensS7Address[] addresses, | ||||||
|         CancellationToken cancellationToken = default) |         CancellationToken cancellationToken = default) | ||||||
|     { |     { | ||||||
|         var byteBuffer = new ValueByteBlock(512); |         return S7ReadAsync(this, addresses, cancellationToken); | ||||||
|  |  | ||||||
|         try |         static async PooledValueTask<OperResult<ReadOnlyMemory<byte>>> S7ReadAsync(SiemensS7Master @this, SiemensS7Address[] addresses, CancellationToken cancellationToken) | ||||||
|         { |         { | ||||||
|             foreach (var address in addresses) |             var byteBuffer = new ValueByteBlock(512); | ||||||
|  |  | ||||||
|  |             try | ||||||
|             { |             { | ||||||
|                 int readCount = 0; |                 foreach (var address in addresses) | ||||||
|                 int totalLength = address.Length == 0 ? 1 : address.Length; |  | ||||||
|                 int originalStart = address.AddressStart; |  | ||||||
|  |  | ||||||
|                 try |  | ||||||
|                 { |                 { | ||||||
|                     while (readCount < totalLength) |                     int readCount = 0; | ||||||
|  |                     int totalLength = address.Length == 0 ? 1 : address.Length; | ||||||
|  |                     int originalStart = address.AddressStart; | ||||||
|  |  | ||||||
|  |                     try | ||||||
|                     { |                     { | ||||||
|                         // 每次读取的 PDU 长度,循环直到读取完整 |                         while (readCount < totalLength) | ||||||
|                         int chunkLength = Math.Min(totalLength - readCount, PduLength); |  | ||||||
|                         address.Length = chunkLength; |  | ||||||
|  |  | ||||||
|                         var result = await SendThenReturnAsync( |  | ||||||
|                             new S7Send([address], true), |  | ||||||
|                             cancellationToken: cancellationToken |  | ||||||
|                         ).ConfigureAwait(false); |  | ||||||
|  |  | ||||||
|                         if (!result.IsSuccess) |  | ||||||
|                             return result; |  | ||||||
|  |  | ||||||
|                         byteBuffer.Write(result.Content.Span); |  | ||||||
|  |  | ||||||
|                         if (readCount + chunkLength >= totalLength) |  | ||||||
|                         { |                         { | ||||||
|                             if (addresses.Length == 1) |                             // 每次读取的 PDU 长度,循环直到读取完整 | ||||||
|                             { |                             int chunkLength = Math.Min(totalLength - readCount, @this.PduLength); | ||||||
|  |                             address.Length = chunkLength; | ||||||
|  |  | ||||||
|  |                             var result = await @this.SendThenReturnAsync( | ||||||
|  |                                 new S7Send([address], true), | ||||||
|  |                                 cancellationToken: cancellationToken | ||||||
|  |                             ).ConfigureAwait(false); | ||||||
|  |  | ||||||
|  |                             if (!result.IsSuccess) | ||||||
|                                 return result; |                                 return result; | ||||||
|  |  | ||||||
|  |                             byteBuffer.Write(result.Content.Span); | ||||||
|  |  | ||||||
|  |                             if (readCount + chunkLength >= totalLength) | ||||||
|  |                             { | ||||||
|  |                                 if (addresses.Length == 1) | ||||||
|  |                                 { | ||||||
|  |                                     return result; | ||||||
|  |                                 } | ||||||
|  |                                 break; | ||||||
|                             } |                             } | ||||||
|                             break; |  | ||||||
|  |                             readCount += chunkLength; | ||||||
|  |  | ||||||
|  |                             // 更新地址起点 | ||||||
|  |                             if (address.DataCode == S7Area.TM || address.DataCode == S7Area.CT) | ||||||
|  |                                 address.AddressStart += chunkLength / 2; | ||||||
|  |                             else | ||||||
|  |                                 address.AddressStart += chunkLength * 8; | ||||||
|                         } |                         } | ||||||
|  |                     } | ||||||
|                         readCount += chunkLength; |                     finally | ||||||
|  |                     { | ||||||
|                         // 更新地址起点 |                         address.AddressStart = originalStart; | ||||||
|                         if (address.DataCode == S7Area.TM || address.DataCode == S7Area.CT) |  | ||||||
|                             address.AddressStart += chunkLength / 2; |  | ||||||
|                         else |  | ||||||
|                             address.AddressStart += chunkLength * 8; |  | ||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
|                 finally |  | ||||||
|                 { |  | ||||||
|                     address.AddressStart = originalStart; |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             return new OperResult<ReadOnlyMemory<byte>> { Content = byteBuffer.ToArray() }; |                 return new OperResult<ReadOnlyMemory<byte>> { Content = byteBuffer.ToArray() }; | ||||||
|         } |             } | ||||||
|         catch (Exception ex) |             catch (Exception ex) | ||||||
|         { |             { | ||||||
|             return new OperResult<ReadOnlyMemory<byte>>(ex); |                 return new OperResult<ReadOnlyMemory<byte>>(ex); | ||||||
|         } |             } | ||||||
|         finally |             finally | ||||||
|         { |             { | ||||||
|             byteBuffer.SafeDispose(); |                 byteBuffer.SafeDispose(); | ||||||
|  |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -216,79 +223,65 @@ public partial class SiemensS7Master : DeviceBase | |||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// 此方法并不会智能分组以最大化效率,减少传输次数,因为返回值是byte[],所以一切都按地址数组的顺序执行,最后合并数组 |     /// 此方法并不会智能分组以最大化效率,减少传输次数,因为返回值是byte[],所以一切都按地址数组的顺序执行,最后合并数组 | ||||||
|     /// </summary> |     /// </summary> | ||||||
|     public async ValueTask<Dictionary<SiemensS7Address, OperResult>> S7WriteAsync( |     public ValueTask<Dictionary<SiemensS7Address, OperResult>> S7WriteAsync( | ||||||
|     SiemensS7Address[] addresses, |     SiemensS7Address[] addresses, | ||||||
|     CancellationToken cancellationToken = default) |     CancellationToken cancellationToken = default) | ||||||
|     { |     { | ||||||
|         var dictOperResult = new Dictionary<SiemensS7Address, OperResult>(); |         return S7WriteAsync(this, addresses, cancellationToken); | ||||||
|  |  | ||||||
|         void SetFailOperResult(OperResult operResult) |         static async PooledValueTask<Dictionary<SiemensS7Address, OperResult>> S7WriteAsync(SiemensS7Master @this, SiemensS7Address[] addresses, CancellationToken cancellationToken) | ||||||
|         { |         { | ||||||
|             foreach (var address in addresses) |             var dictOperResult = new Dictionary<SiemensS7Address, OperResult>(); | ||||||
|             { |  | ||||||
|                 dictOperResult.TryAdd(address, operResult); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         var firstAddress = addresses[0]; |             void SetFailOperResult(OperResult operResult) | ||||||
|  |  | ||||||
|         // 单位写入(位写入) |  | ||||||
|         if (addresses.Length <= 1 && firstAddress.IsBit) |  | ||||||
|         { |  | ||||||
|             var byteBuffer = new ValueByteBlock(512); |  | ||||||
|             try |  | ||||||
|             { |             { | ||||||
|                 var writeResult = await SendThenReturnAsync( |                 foreach (var address in addresses) | ||||||
|                     new S7Send([firstAddress], false), |  | ||||||
|                     cancellationToken: cancellationToken |  | ||||||
|                 ).ConfigureAwait(false); |  | ||||||
|  |  | ||||||
|                 dictOperResult.TryAdd(firstAddress, writeResult); |  | ||||||
|                 return dictOperResult; |  | ||||||
|             } |  | ||||||
|             catch (Exception ex) |  | ||||||
|             { |  | ||||||
|                 SetFailOperResult(new OperResult(ex)); |  | ||||||
|                 return dictOperResult; |  | ||||||
|             } |  | ||||||
|             finally |  | ||||||
|             { |  | ||||||
|                 byteBuffer.SafeDispose(); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|         else |  | ||||||
|         { |  | ||||||
|             // 多写入 |  | ||||||
|             var addressChunks = new List<List<SiemensS7Address>>(); |  | ||||||
|             ushort dataLength = 0; |  | ||||||
|             ushort itemCount = 1; |  | ||||||
|             var currentChunk = new List<SiemensS7Address>(); |  | ||||||
|  |  | ||||||
|             for (int i = 0; i < addresses.Length; i++) |  | ||||||
|             { |  | ||||||
|                 var address = addresses[i]; |  | ||||||
|                 dataLength += (ushort)(address.Data.Length + 4); |  | ||||||
|                 ushort telegramLength = (ushort)(itemCount * 12 + 19 + dataLength); |  | ||||||
|  |  | ||||||
|                 if (telegramLength < PduLength) |  | ||||||
|                 { |                 { | ||||||
|                     currentChunk.Add(address); |                     dictOperResult.TryAdd(address, operResult); | ||||||
|                     itemCount++; |  | ||||||
|  |  | ||||||
|                     if (i == addresses.Length - 1) |  | ||||||
|                         addressChunks.Add(currentChunk); |  | ||||||
|                 } |                 } | ||||||
|                 else |             } | ||||||
|  |  | ||||||
|  |             var firstAddress = addresses[0]; | ||||||
|  |  | ||||||
|  |             // 单位写入(位写入) | ||||||
|  |             if (addresses.Length <= 1 && firstAddress.IsBit) | ||||||
|  |             { | ||||||
|  |                 var byteBuffer = new ValueByteBlock(512); | ||||||
|  |                 try | ||||||
|                 { |                 { | ||||||
|                     addressChunks.Add(currentChunk); |                     var writeResult = await @this.SendThenReturnAsync( | ||||||
|                     currentChunk = new List<SiemensS7Address>(); |                         new S7Send([firstAddress], false), | ||||||
|                     dataLength = 0; |                         cancellationToken: cancellationToken | ||||||
|                     itemCount = 1; |                     ).ConfigureAwait(false); | ||||||
|  |  | ||||||
|  |                     dictOperResult.TryAdd(firstAddress, writeResult); | ||||||
|  |                     return dictOperResult; | ||||||
|  |                 } | ||||||
|  |                 catch (Exception ex) | ||||||
|  |                 { | ||||||
|  |                     SetFailOperResult(new OperResult(ex)); | ||||||
|  |                     return dictOperResult; | ||||||
|  |                 } | ||||||
|  |                 finally | ||||||
|  |                 { | ||||||
|  |                     byteBuffer.SafeDispose(); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             else | ||||||
|  |             { | ||||||
|  |                 // 多写入 | ||||||
|  |                 var addressChunks = new List<List<SiemensS7Address>>(); | ||||||
|  |                 ushort dataLength = 0; | ||||||
|  |                 ushort itemCount = 1; | ||||||
|  |                 var currentChunk = new List<SiemensS7Address>(); | ||||||
|  |  | ||||||
|  |                 for (int i = 0; i < addresses.Length; i++) | ||||||
|  |                 { | ||||||
|  |                     var address = addresses[i]; | ||||||
|                     dataLength += (ushort)(address.Data.Length + 4); |                     dataLength += (ushort)(address.Data.Length + 4); | ||||||
|                     telegramLength = (ushort)(itemCount * 12 + 19 + dataLength); |                     ushort telegramLength = (ushort)(itemCount * 12 + 19 + dataLength); | ||||||
|  |  | ||||||
|                     if (telegramLength < PduLength) |                     if (telegramLength < @this.PduLength) | ||||||
|                     { |                     { | ||||||
|                         currentChunk.Add(address); |                         currentChunk.Add(address); | ||||||
|                         itemCount++; |                         itemCount++; | ||||||
| @@ -298,34 +291,53 @@ public partial class SiemensS7Master : DeviceBase | |||||||
|                     } |                     } | ||||||
|                     else |                     else | ||||||
|                     { |                     { | ||||||
|                         SetFailOperResult(new OperResult("Write length exceeds limit")); |                         addressChunks.Add(currentChunk); | ||||||
|  |                         currentChunk = new List<SiemensS7Address>(); | ||||||
|  |                         dataLength = 0; | ||||||
|  |                         itemCount = 1; | ||||||
|  |  | ||||||
|  |                         dataLength += (ushort)(address.Data.Length + 4); | ||||||
|  |                         telegramLength = (ushort)(itemCount * 12 + 19 + dataLength); | ||||||
|  |  | ||||||
|  |                         if (telegramLength < @this.PduLength) | ||||||
|  |                         { | ||||||
|  |                             currentChunk.Add(address); | ||||||
|  |                             itemCount++; | ||||||
|  |  | ||||||
|  |                             if (i == addresses.Length - 1) | ||||||
|  |                                 addressChunks.Add(currentChunk); | ||||||
|  |                         } | ||||||
|  |                         else | ||||||
|  |                         { | ||||||
|  |                             SetFailOperResult(new OperResult("Write length exceeds limit")); | ||||||
|  |                             return dictOperResult; | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 foreach (var chunk in addressChunks) | ||||||
|  |                 { | ||||||
|  |                     try | ||||||
|  |                     { | ||||||
|  |                         var result = await @this.SendThenReturnAsync( | ||||||
|  |                             new S7Send(chunk.ToArray(), false), | ||||||
|  |                             cancellationToken: cancellationToken | ||||||
|  |                         ).ConfigureAwait(false); | ||||||
|  |  | ||||||
|  |                         foreach (var addr in chunk) | ||||||
|  |                         { | ||||||
|  |                             dictOperResult.TryAdd(addr, result); | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                     catch (Exception ex) | ||||||
|  |                     { | ||||||
|  |                         SetFailOperResult(new OperResult(ex)); | ||||||
|                         return dictOperResult; |                         return dictOperResult; | ||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
|  |  | ||||||
|  |                 return dictOperResult; | ||||||
|             } |             } | ||||||
|  |  | ||||||
|             foreach (var chunk in addressChunks) |  | ||||||
|             { |  | ||||||
|                 try |  | ||||||
|                 { |  | ||||||
|                     var result = await SendThenReturnAsync( |  | ||||||
|                         new S7Send(chunk.ToArray(), false), |  | ||||||
|                         cancellationToken: cancellationToken |  | ||||||
|                     ).ConfigureAwait(false); |  | ||||||
|  |  | ||||||
|                     foreach (var addr in chunk) |  | ||||||
|                     { |  | ||||||
|                         dictOperResult.TryAdd(addr, result); |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|                 catch (Exception ex) |  | ||||||
|                 { |  | ||||||
|                     SetFailOperResult(new OperResult(ex)); |  | ||||||
|                     return dictOperResult; |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             return dictOperResult; |  | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -8,6 +8,8 @@ | |||||||
| //  QQ群:605534569 | //  QQ群:605534569 | ||||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||||
|  |  | ||||||
|  | using PooledAwait; | ||||||
|  |  | ||||||
| using System.Diagnostics; | using System.Diagnostics; | ||||||
|  |  | ||||||
| using ThingsGateway.Extension.Generic; | using ThingsGateway.Extension.Generic; | ||||||
| @@ -81,91 +83,101 @@ public partial class QuestDBProducer : BusinessBaseWithCacheIntervalVariable | |||||||
|             AddQueueVarModel(new CacheDBItem<VariableBasicData>(variable)); |             AddQueueVarModel(new CacheDBItem<VariableBasicData>(variable)); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|     private async ValueTask<OperResult> UpdateVarModel(IEnumerable<VariableBasicData> item, CancellationToken cancellationToken) |     private ValueTask<OperResult> UpdateVarModel(IEnumerable<VariableBasicData> item, CancellationToken cancellationToken) | ||||||
|     { |     { | ||||||
|         var result = await InserableAsync(item.WhereIf(_driverPropertys.OnlineFilter, a => a.IsOnline == true).ToList(), cancellationToken).ConfigureAwait(false); |         return UpdateVarModel(this, item, cancellationToken); | ||||||
|         if (success != result.IsSuccess) |  | ||||||
|         { |  | ||||||
|             if (!result.IsSuccess) |  | ||||||
|                 LogMessage?.LogWarning(result.ToString()); |  | ||||||
|             success = result.IsSuccess; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         return result; |         static async PooledValueTask<OperResult> UpdateVarModel(QuestDBProducer @this, IEnumerable<VariableBasicData> item, CancellationToken cancellationToken) | ||||||
|  |         { | ||||||
|  |             var result = await @this.InserableAsync(item.WhereIf(@this._driverPropertys.OnlineFilter, a => a.IsOnline == true).ToList(), cancellationToken).ConfigureAwait(false); | ||||||
|  |             if (@this.success != result.IsSuccess) | ||||||
|  |             { | ||||||
|  |                 if (!result.IsSuccess) | ||||||
|  |                     @this.LogMessage?.LogWarning(result.ToString()); | ||||||
|  |                 @this.success = result.IsSuccess; | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             return result; | ||||||
|  |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     #region 方法 |     #region 方法 | ||||||
|  |  | ||||||
|     private async ValueTask<OperResult> InserableAsync(List<VariableBasicData> dbInserts, CancellationToken cancellationToken) |     private ValueTask<OperResult> InserableAsync(List<VariableBasicData> dbInserts, CancellationToken cancellationToken) | ||||||
|     { |     { | ||||||
|         try |         return InserableAsync(this, dbInserts, cancellationToken); | ||||||
|  |  | ||||||
|  |         static async PooledValueTask<OperResult> InserableAsync(QuestDBProducer @this, List<VariableBasicData> dbInserts, CancellationToken cancellationToken) | ||||||
|         { |         { | ||||||
|             _db.Ado.CancellationToken = cancellationToken; |             try | ||||||
|             if (!_driverPropertys.BigTextScriptHistoryTable.IsNullOrEmpty()) |  | ||||||
|             { |             { | ||||||
|                 var getDeviceModel = CSharpScriptEngineExtension.Do<DynamicSQLBase>(_driverPropertys.BigTextScriptHistoryTable); |                 @this._db.Ado.CancellationToken = cancellationToken; | ||||||
|                 getDeviceModel.Logger = LogMessage; |                 if (!@this._driverPropertys.BigTextScriptHistoryTable.IsNullOrEmpty()) | ||||||
|  |  | ||||||
|                 await getDeviceModel.DBInsertable(_db, dbInserts, cancellationToken).ConfigureAwait(false); |  | ||||||
|             } |  | ||||||
|             else |  | ||||||
|             { |  | ||||||
|                 var stringData = dbInserts.Where(a => (!a.IsNumber && a.Value is not bool)); |  | ||||||
|                 var numberData = dbInserts.Where(a => (a.IsNumber || a.Value is bool)); |  | ||||||
|  |  | ||||||
|                 if (numberData.Any()) |  | ||||||
|                 { |                 { | ||||||
|                     Stopwatch stopwatch = new(); |                     var getDeviceModel = CSharpScriptEngineExtension.Do<DynamicSQLBase>(@this._driverPropertys.BigTextScriptHistoryTable); | ||||||
|                     stopwatch.Start(); |                     getDeviceModel.Logger = @this.LogMessage; | ||||||
|                     var data = numberData.AdaptListQuestDBNumberHistoryValue(); |  | ||||||
|                     int result = 0; |  | ||||||
|                     if (_driverPropertys.RestApi) |  | ||||||
|                     { |  | ||||||
|                         result = await _db.RestApi(_driverPropertys.HttpPort).BulkCopyAsync(data, _driverPropertys.NumberTableName).ConfigureAwait(false);//不要加分表 |  | ||||||
|                     } |  | ||||||
|                     else |  | ||||||
|                     { |  | ||||||
|                         result = await _db.Insertable(data).AS(_driverPropertys.NumberTableName).ExecuteCommandAsync(cancellationToken).ConfigureAwait(false);//不要加分表 |  | ||||||
|                     } |  | ||||||
|                     stopwatch.Stop(); |  | ||||||
|  |  | ||||||
|                     //var result = await db.Insertable(dbInserts).SplitTable().ExecuteCommandAsync().ConfigureAwait(false); |                     await getDeviceModel.DBInsertable(@this._db, dbInserts, cancellationToken).ConfigureAwait(false); | ||||||
|                     if (result > 0) |                 } | ||||||
|  |                 else | ||||||
|  |                 { | ||||||
|  |                     var stringData = dbInserts.Where(a => (!a.IsNumber && a.Value is not bool)); | ||||||
|  |                     var numberData = dbInserts.Where(a => (a.IsNumber || a.Value is bool)); | ||||||
|  |  | ||||||
|  |                     if (numberData.Any()) | ||||||
|                     { |                     { | ||||||
|                         LogMessage?.Trace($"TableName:{_driverPropertys.NumberTableName},Count:{result},watchTime:  {stopwatch.ElapsedMilliseconds} ms"); |                         Stopwatch stopwatch = new(); | ||||||
|  |                         stopwatch.Start(); | ||||||
|  |                         var data = numberData.AdaptListQuestDBNumberHistoryValue(); | ||||||
|  |                         int result = 0; | ||||||
|  |                         if (@this._driverPropertys.RestApi) | ||||||
|  |                         { | ||||||
|  |                             result = await @this._db.RestApi(@this._driverPropertys.HttpPort).BulkCopyAsync(data, @this._driverPropertys.NumberTableName).ConfigureAwait(false);//不要加分表 | ||||||
|  |                         } | ||||||
|  |                         else | ||||||
|  |                         { | ||||||
|  |                             result = await @this._db.Insertable(data).AS(@this._driverPropertys.NumberTableName).ExecuteCommandAsync(cancellationToken).ConfigureAwait(false);//不要加分表 | ||||||
|  |                         } | ||||||
|  |                         stopwatch.Stop(); | ||||||
|  |  | ||||||
|  |                         //var result = await db.Insertable(dbInserts).SplitTable().ExecuteCommandAsync().ConfigureAwait(false); | ||||||
|  |                         if (result > 0) | ||||||
|  |                         { | ||||||
|  |                             @this.LogMessage?.Trace($"TableName:{@this._driverPropertys.NumberTableName},Count:{result},watchTime:  {stopwatch.ElapsedMilliseconds} ms"); | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |  | ||||||
|  |                     if (stringData.Any()) | ||||||
|  |                     { | ||||||
|  |                         Stopwatch stopwatch = new(); | ||||||
|  |                         stopwatch.Start(); | ||||||
|  |                         var data = stringData.AdaptListQuestDBHistoryValue(); | ||||||
|  |                         int result = 0; | ||||||
|  |                         if (@this._driverPropertys.RestApi) | ||||||
|  |                         { | ||||||
|  |                             result = await @this._db.RestApi(@this._driverPropertys.HttpPort).BulkCopyAsync(data, @this._driverPropertys.StringTableName).ConfigureAwait(false);//不要加分表 | ||||||
|  |                         } | ||||||
|  |                         else | ||||||
|  |                         { | ||||||
|  |                             result = await @this._db.Insertable(data).AS(@this._driverPropertys.StringTableName).ExecuteCommandAsync(cancellationToken).ConfigureAwait(false);//不要加分表 | ||||||
|  |                         } | ||||||
|  |  | ||||||
|  |                         stopwatch.Stop(); | ||||||
|  |  | ||||||
|  |                         //var result = await db.Insertable(dbInserts).SplitTable().ExecuteCommandAsync().ConfigureAwait(false); | ||||||
|  |                         if (result > 0) | ||||||
|  |                         { | ||||||
|  |                             @this.LogMessage?.Trace($"TableName:{@this._driverPropertys.StringTableName},Count:{result},watchTime:  {stopwatch.ElapsedMilliseconds} ms"); | ||||||
|  |                         } | ||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
|  |  | ||||||
|                 if (stringData.Any()) |                 return OperResult.Success; | ||||||
|                 { |             } | ||||||
|                     Stopwatch stopwatch = new(); |             catch (Exception ex) | ||||||
|                     stopwatch.Start(); |             { | ||||||
|                     var data = stringData.AdaptListQuestDBHistoryValue(); |                 return new OperResult(ex); | ||||||
|                     int result = 0; |  | ||||||
|                     if (_driverPropertys.RestApi) |  | ||||||
|                     { |  | ||||||
|                         result = await _db.RestApi(_driverPropertys.HttpPort).BulkCopyAsync(data, _driverPropertys.StringTableName).ConfigureAwait(false);//不要加分表 |  | ||||||
|                     } |  | ||||||
|                     else |  | ||||||
|                     { |  | ||||||
|                         result = await _db.Insertable(data).AS(_driverPropertys.StringTableName).ExecuteCommandAsync(cancellationToken).ConfigureAwait(false);//不要加分表 |  | ||||||
|                     } |  | ||||||
|  |  | ||||||
|                     stopwatch.Stop(); |  | ||||||
|  |  | ||||||
|                     //var result = await db.Insertable(dbInserts).SplitTable().ExecuteCommandAsync().ConfigureAwait(false); |  | ||||||
|                     if (result > 0) |  | ||||||
|                     { |  | ||||||
|                         LogMessage?.Trace($"TableName:{_driverPropertys.StringTableName},Count:{result},watchTime:  {stopwatch.ElapsedMilliseconds} ms"); |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|             } |             } | ||||||
|  |  | ||||||
|             return OperResult.Success; |  | ||||||
|         } |  | ||||||
|         catch (Exception ex) |  | ||||||
|         { |  | ||||||
|             return new OperResult(ex); |  | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -10,6 +10,8 @@ | |||||||
|  |  | ||||||
| using BootstrapBlazor.Components; | using BootstrapBlazor.Components; | ||||||
|  |  | ||||||
|  | using PooledAwait; | ||||||
|  |  | ||||||
| using ThingsGateway.Common; | using ThingsGateway.Common; | ||||||
| using ThingsGateway.DB; | using ThingsGateway.DB; | ||||||
| using ThingsGateway.Debug; | using ThingsGateway.Debug; | ||||||
| @@ -239,39 +241,44 @@ public partial class SqlDBProducer : BusinessBaseWithCacheIntervalVariable | |||||||
|         await base.ProtectedStartAsync(cancellationToken).ConfigureAwait(false); |         await base.ProtectedStartAsync(cancellationToken).ConfigureAwait(false); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     protected override async Task ProtectedExecuteAsync(object? state, CancellationToken cancellationToken) |     protected override Task ProtectedExecuteAsync(object? state, CancellationToken cancellationToken) | ||||||
|     { |     { | ||||||
|         if (_driverPropertys.IsReadDB) |         return ProtectedExecuteAsync(this, cancellationToken); | ||||||
|  |  | ||||||
|  |         static async PooledTask ProtectedExecuteAsync(SqlDBProducer @this, CancellationToken cancellationToken) | ||||||
|         { |         { | ||||||
|             var list = RealTimeVariables.ToListWithDequeue(); |             if (@this._driverPropertys.IsReadDB) | ||||||
|             try |  | ||||||
|             { |             { | ||||||
|                 var varLists = list.Batch(_driverPropertys.SplitSize); |                 var list = @this.RealTimeVariables.ToListWithDequeue(); | ||||||
|                 foreach (var varList in varLists) |                 try | ||||||
|                 { |                 { | ||||||
|                     var result = await UpdateAsync(varList, cancellationToken).ConfigureAwait(false); |                     var varLists = list.Batch(@this._driverPropertys.SplitSize); | ||||||
|                     if (success != result.IsSuccess) |                     foreach (var varList in varLists) | ||||||
|                     { |                     { | ||||||
|                         if (!result.IsSuccess) |                         var result = await @this.UpdateAsync(varList, cancellationToken).ConfigureAwait(false); | ||||||
|                             LogMessage?.LogWarning(result.ToString()); |                         if (@this.success != result.IsSuccess) | ||||||
|                         success = result.IsSuccess; |                         { | ||||||
|  |                             if (!result.IsSuccess) | ||||||
|  |                                 @this.LogMessage?.LogWarning(result.ToString()); | ||||||
|  |                             @this.success = result.IsSuccess; | ||||||
|  |                         } | ||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
|  |                 catch (Exception ex) | ||||||
|  |                 { | ||||||
|  |                     if (@this.success) | ||||||
|  |                         @this.LogMessage?.LogWarning(ex); | ||||||
|  |                     @this.success = false; | ||||||
|  |  | ||||||
|  |                     list.ForEach(variable => @this.RealTimeVariables.AddOrUpdate(variable.Id, variable, (key, oldValue) => variable)); | ||||||
|  |                 } | ||||||
|             } |             } | ||||||
|             catch (Exception ex) |  | ||||||
|  |             if (@this._driverPropertys.IsHistoryDB) | ||||||
|             { |             { | ||||||
|                 if (success) |                 await @this.Update(cancellationToken).ConfigureAwait(false); | ||||||
|                     LogMessage?.LogWarning(ex); |  | ||||||
|                 success = false; |  | ||||||
|  |  | ||||||
|                 list.ForEach(variable => RealTimeVariables.AddOrUpdate(variable.Id, variable, (key, oldValue) => variable)); |  | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         if (_driverPropertys.IsHistoryDB) |  | ||||||
|         { |  | ||||||
|             await Update(cancellationToken).ConfigureAwait(false); |  | ||||||
|         } |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private ISugarQueryable<SQLNumberHistoryValue> Query(DBHistoryValuePageInput input) |     private ISugarQueryable<SQLNumberHistoryValue> Query(DBHistoryValuePageInput input) | ||||||
|   | |||||||
| @@ -8,6 +8,8 @@ | |||||||
| //  QQ群:605534569 | //  QQ群:605534569 | ||||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||||
|  |  | ||||||
|  | using PooledAwait; | ||||||
|  |  | ||||||
| using System.Collections.Concurrent; | using System.Collections.Concurrent; | ||||||
| using System.Diagnostics; | using System.Diagnostics; | ||||||
|  |  | ||||||
| @@ -97,131 +99,146 @@ public partial class SqlDBProducer : BusinessBaseWithCacheIntervalVariable | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private async ValueTask<OperResult> UpdateVarModel(IEnumerable<VariableBasicData> item, CancellationToken cancellationToken) |     private ValueTask<OperResult> UpdateVarModel(IEnumerable<VariableBasicData> item, CancellationToken cancellationToken) | ||||||
|     { |     { | ||||||
|         var result = await InserableAsync(item.WhereIf(_driverPropertys.OnlineFilter, a => a.IsOnline == true).ToList(), cancellationToken).ConfigureAwait(false); |         return UpdateVarModel(this, item, cancellationToken); | ||||||
|         if (success != result.IsSuccess) |  | ||||||
|         { |  | ||||||
|             if (!result.IsSuccess) |  | ||||||
|                 LogMessage?.LogWarning(result.ToString()); |  | ||||||
|             success = result.IsSuccess; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         return result; |         static async PooledValueTask<OperResult> UpdateVarModel(SqlDBProducer @this, IEnumerable<VariableBasicData> item, CancellationToken cancellationToken) | ||||||
|  |         { | ||||||
|  |             var result = await @this.InserableAsync(item.WhereIf(@this._driverPropertys.OnlineFilter, a => a.IsOnline == true).ToList(), cancellationToken).ConfigureAwait(false); | ||||||
|  |             if (@this.success != result.IsSuccess) | ||||||
|  |             { | ||||||
|  |                 if (!result.IsSuccess) | ||||||
|  |                     @this.LogMessage?.LogWarning(result.ToString()); | ||||||
|  |                 @this.success = result.IsSuccess; | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             return result; | ||||||
|  |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     #region 方法 |     #region 方法 | ||||||
|  |  | ||||||
|     private async ValueTask<OperResult> InserableAsync(List<VariableBasicData> dbInserts, CancellationToken cancellationToken) |     private ValueTask<OperResult> InserableAsync(List<VariableBasicData> dbInserts, CancellationToken cancellationToken) | ||||||
|     { |     { | ||||||
|         try |         return InserableAsync(this, dbInserts, cancellationToken); | ||||||
|  |  | ||||||
|  |         static async PooledValueTask<OperResult> InserableAsync(SqlDBProducer @this, List<VariableBasicData> dbInserts, CancellationToken cancellationToken) | ||||||
|         { |         { | ||||||
|             _db.Ado.CancellationToken = cancellationToken; |             try | ||||||
|             if (!_driverPropertys.BigTextScriptHistoryTable.IsNullOrEmpty()) |  | ||||||
|             { |             { | ||||||
|                 var getDeviceModel = CSharpScriptEngineExtension.Do<DynamicSQLBase>(_driverPropertys.BigTextScriptHistoryTable); |                 @this._db.Ado.CancellationToken = cancellationToken; | ||||||
|  |                 if (!@this._driverPropertys.BigTextScriptHistoryTable.IsNullOrEmpty()) | ||||||
|                 getDeviceModel.Logger = LogMessage; |  | ||||||
|  |  | ||||||
|                 await getDeviceModel.DBInsertable(_db, dbInserts, cancellationToken).ConfigureAwait(false); |  | ||||||
|             } |  | ||||||
|             else |  | ||||||
|             { |  | ||||||
|                 var stringData = dbInserts.Where(a => (!a.IsNumber && a.Value is not bool)); |  | ||||||
|                 var numberData = dbInserts.Where(a => (a.IsNumber || a.Value is bool)); |  | ||||||
|  |  | ||||||
|                 if (numberData.Any()) |  | ||||||
|                 { |                 { | ||||||
|                     var data = numberData.AdaptEnumerableSQLNumberHistoryValue(); |                     var getDeviceModel = CSharpScriptEngineExtension.Do<DynamicSQLBase>(@this._driverPropertys.BigTextScriptHistoryTable); | ||||||
|                     Stopwatch stopwatch = new(); |  | ||||||
|                     stopwatch.Start(); |  | ||||||
|                     var result = await _db.Fastest<SQLNumberHistoryValue>().SplitTable().BulkCopyAsync(data).ConfigureAwait(false); |  | ||||||
|                     stopwatch.Stop(); |  | ||||||
|  |  | ||||||
|                     //var result = await db.Insertable(dbInserts).SplitTable().ExecuteCommandAsync().ConfigureAwait(false); |                     getDeviceModel.Logger = @this.LogMessage; | ||||||
|                     if (result > 0) |  | ||||||
|                     { |                     await getDeviceModel.DBInsertable(@this._db, dbInserts, cancellationToken).ConfigureAwait(false); | ||||||
|                         LogMessage?.Trace($"TableName:{_driverPropertys.NumberTableName},Count:{result},watchTime:  {stopwatch.ElapsedMilliseconds} ms"); |  | ||||||
|                     } |  | ||||||
|                 } |                 } | ||||||
|  |                 else | ||||||
|                 if (stringData.Any()) |  | ||||||
|                 { |                 { | ||||||
|                     Stopwatch stopwatch = new(); |                     var stringData = dbInserts.Where(a => (!a.IsNumber && a.Value is not bool)); | ||||||
|                     stopwatch.Start(); |                     var numberData = dbInserts.Where(a => (a.IsNumber || a.Value is bool)); | ||||||
|                     var data = stringData.AdaptEnumerableSQLHistoryValue(); |  | ||||||
|                     var result = await _db.Fastest<SQLHistoryValue>().SplitTable().BulkCopyAsync(data).ConfigureAwait(false); |  | ||||||
|                     stopwatch.Stop(); |  | ||||||
|  |  | ||||||
|                     //var result = await db.Insertable(dbInserts).SplitTable().ExecuteCommandAsync().ConfigureAwait(false); |                     if (numberData.Any()) | ||||||
|                     if (result > 0) |  | ||||||
|                     { |                     { | ||||||
|                         LogMessage?.Trace($"TableName:{_driverPropertys.StringTableName},Count:{result},watchTime:  {stopwatch.ElapsedMilliseconds} ms"); |                         var data = numberData.AdaptEnumerableSQLNumberHistoryValue(); | ||||||
|  |                         Stopwatch stopwatch = new(); | ||||||
|  |                         stopwatch.Start(); | ||||||
|  |                         var result = await @this._db.Fastest<SQLNumberHistoryValue>().SplitTable().BulkCopyAsync(data).ConfigureAwait(false); | ||||||
|  |                         stopwatch.Stop(); | ||||||
|  |  | ||||||
|  |                         //var result = await db.Insertable(dbInserts).SplitTable().ExecuteCommandAsync().ConfigureAwait(false); | ||||||
|  |                         if (result > 0) | ||||||
|  |                         { | ||||||
|  |                             @this.LogMessage?.Trace($"TableName:{@this._driverPropertys.NumberTableName},Count:{result},watchTime:  {stopwatch.ElapsedMilliseconds} ms"); | ||||||
|  |                         } | ||||||
|                     } |                     } | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             return OperResult.Success; |                     if (stringData.Any()) | ||||||
|         } |  | ||||||
|         catch (Exception ex) |  | ||||||
|         { |  | ||||||
|             return new OperResult(ex); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private async ValueTask<OperResult> UpdateAsync(List<VariableBasicData> datas, CancellationToken cancellationToken) |  | ||||||
|     { |  | ||||||
|         try |  | ||||||
|         { |  | ||||||
|             _db.Ado.CancellationToken = cancellationToken; |  | ||||||
|  |  | ||||||
|             if (!_driverPropertys.BigTextScriptRealTable.IsNullOrEmpty()) |  | ||||||
|             { |  | ||||||
|                 var getDeviceModel = CSharpScriptEngineExtension.Do<DynamicSQLBase>(_driverPropertys.BigTextScriptRealTable); |  | ||||||
|                 getDeviceModel.Logger = LogMessage; |  | ||||||
|  |  | ||||||
|                 await getDeviceModel.DBInsertable(_db, datas, cancellationToken).ConfigureAwait(false); |  | ||||||
|                 return OperResult.Success; |  | ||||||
|             } |  | ||||||
|             else |  | ||||||
|             { |  | ||||||
|                 if (!_initRealData) |  | ||||||
|                 { |  | ||||||
|                     Stopwatch stopwatch = new(); |  | ||||||
|                     stopwatch.Start(); |  | ||||||
|                     var ids = (await _db.Queryable<SQLRealValue>().AS(_driverPropertys.ReadDBTableName).Select(a => a.Id).ToListAsync(cancellationToken).ConfigureAwait(false)).ToHashSet(); |  | ||||||
|                     var InsertData = IdVariableRuntimes.Where(a => !ids.Contains(a.Key)).Select(a => a.Value).AdaptEnumerableSQLRealValue(); |  | ||||||
|                     var result = await _db.Fastest<SQLRealValue>().AS(_driverPropertys.ReadDBTableName).BulkCopyAsync(InsertData).ConfigureAwait(false); |  | ||||||
|                     _initRealData = true; |  | ||||||
|                     stopwatch.Stop(); |  | ||||||
|                     if (result > 0) |  | ||||||
|                     { |  | ||||||
|                         LogMessage?.Trace($"RealTable Insert Data Count:{result},watchTime:  {stopwatch.ElapsedMilliseconds} ms"); |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|                 { |  | ||||||
|                     if (datas?.Count > 0) |  | ||||||
|                     { |                     { | ||||||
|                         Stopwatch stopwatch = new(); |                         Stopwatch stopwatch = new(); | ||||||
|                         stopwatch.Start(); |                         stopwatch.Start(); | ||||||
|  |                         var data = stringData.AdaptEnumerableSQLHistoryValue(); | ||||||
|  |                         var result = await @this._db.Fastest<SQLHistoryValue>().SplitTable().BulkCopyAsync(data).ConfigureAwait(false); | ||||||
|  |                         stopwatch.Stop(); | ||||||
|  |  | ||||||
|                         var data = datas.AdaptEnumerableSQLRealValue(); |                         //var result = await db.Insertable(dbInserts).SplitTable().ExecuteCommandAsync().ConfigureAwait(false); | ||||||
|                         var result = await _db.Fastest<SQLRealValue>().AS(_driverPropertys.ReadDBTableName).BulkUpdateAsync(data).ConfigureAwait(false); |                         if (result > 0) | ||||||
|  |                         { | ||||||
|  |                             @this.LogMessage?.Trace($"TableName:{@this._driverPropertys.StringTableName},Count:{result},watchTime:  {stopwatch.ElapsedMilliseconds} ms"); | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 return OperResult.Success; | ||||||
|  |             } | ||||||
|  |             catch (Exception ex) | ||||||
|  |             { | ||||||
|  |                 return new OperResult(ex); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private ValueTask<OperResult> UpdateAsync(List<VariableBasicData> datas, CancellationToken cancellationToken) | ||||||
|  |     { | ||||||
|  |         return UpdateAsync(this, datas, cancellationToken); | ||||||
|  |  | ||||||
|  |         static async PooledValueTask<OperResult> UpdateAsync(SqlDBProducer @this, List<VariableBasicData> datas, CancellationToken cancellationToken) | ||||||
|  |         { | ||||||
|  |             try | ||||||
|  |             { | ||||||
|  |                 @this._db.Ado.CancellationToken = cancellationToken; | ||||||
|  |  | ||||||
|  |                 if (!@this._driverPropertys.BigTextScriptRealTable.IsNullOrEmpty()) | ||||||
|  |                 { | ||||||
|  |                     var getDeviceModel = CSharpScriptEngineExtension.Do<DynamicSQLBase>(@this._driverPropertys.BigTextScriptRealTable); | ||||||
|  |                     getDeviceModel.Logger = @this.LogMessage; | ||||||
|  |  | ||||||
|  |                     await getDeviceModel.DBInsertable(@this._db, datas, cancellationToken).ConfigureAwait(false); | ||||||
|  |                     return OperResult.Success; | ||||||
|  |                 } | ||||||
|  |                 else | ||||||
|  |                 { | ||||||
|  |                     if (!@this._initRealData) | ||||||
|  |                     { | ||||||
|  |                         Stopwatch stopwatch = new(); | ||||||
|  |                         stopwatch.Start(); | ||||||
|  |                         var ids = (await @this._db.Queryable<SQLRealValue>().AS(@this._driverPropertys.ReadDBTableName).Select(a => a.Id).ToListAsync(cancellationToken).ConfigureAwait(false)).ToHashSet(); | ||||||
|  |                         var InsertData = @this.IdVariableRuntimes.Where(a => !ids.Contains(a.Key)).Select(a => a.Value).AdaptEnumerableSQLRealValue(); | ||||||
|  |                         var result = await @this._db.Fastest<SQLRealValue>().AS(@this._driverPropertys.ReadDBTableName).BulkCopyAsync(InsertData).ConfigureAwait(false); | ||||||
|  |                         @this._initRealData = true; | ||||||
|                         stopwatch.Stop(); |                         stopwatch.Stop(); | ||||||
|                         if (result > 0) |                         if (result > 0) | ||||||
|                         { |                         { | ||||||
|                             LogMessage?.Trace($"RealTable Data Count:{result},watchTime:  {stopwatch.ElapsedMilliseconds} ms"); |                             @this.LogMessage?.Trace($"RealTable Insert Data Count:{result},watchTime:  {stopwatch.ElapsedMilliseconds} ms"); | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                     { | ||||||
|  |                         if (datas?.Count > 0) | ||||||
|  |                         { | ||||||
|  |                             Stopwatch stopwatch = new(); | ||||||
|  |                             stopwatch.Start(); | ||||||
|  |  | ||||||
|  |                             var data = datas.AdaptEnumerableSQLRealValue(); | ||||||
|  |                             var result = await @this._db.Fastest<SQLRealValue>().AS(@this._driverPropertys.ReadDBTableName).BulkUpdateAsync(data).ConfigureAwait(false); | ||||||
|  |  | ||||||
|  |                             stopwatch.Stop(); | ||||||
|  |                             if (result > 0) | ||||||
|  |                             { | ||||||
|  |                                 @this.LogMessage?.Trace($"RealTable Data Count:{result},watchTime:  {stopwatch.ElapsedMilliseconds} ms"); | ||||||
|  |                             } | ||||||
|  |                             return OperResult.Success; | ||||||
|                         } |                         } | ||||||
|                         return OperResult.Success; |                         return OperResult.Success; | ||||||
|                     } |                     } | ||||||
|                     return OperResult.Success; |  | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|         } |             catch (Exception ex) | ||||||
|         catch (Exception ex) |             { | ||||||
|         { |                 return new OperResult(ex); | ||||||
|             return new OperResult(ex); |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -8,6 +8,8 @@ | |||||||
| //  QQ群:605534569 | //  QQ群:605534569 | ||||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||||
|  |  | ||||||
|  | using PooledAwait; | ||||||
|  |  | ||||||
| using System.Diagnostics; | using System.Diagnostics; | ||||||
| using System.Text; | using System.Text; | ||||||
|  |  | ||||||
| @@ -85,66 +87,80 @@ public partial class TDengineDBProducer : BusinessBaseWithCacheIntervalVariable | |||||||
|             AddQueueVarModel(new CacheDBItem<VariableBasicData>(variable)); |             AddQueueVarModel(new CacheDBItem<VariableBasicData>(variable)); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|     private async ValueTask<OperResult> UpdateVarModel(IEnumerable<VariableBasicData> item, CancellationToken cancellationToken) |     private ValueTask<OperResult> UpdateVarModel(IEnumerable<VariableBasicData> item, CancellationToken cancellationToken) | ||||||
|     { |     { | ||||||
|         var result = await InserableAsync(item.WhereIf(_driverPropertys.OnlineFilter, a => a.IsOnline == true).ToList(), cancellationToken).ConfigureAwait(false); |         return UpdateVarModel(this, item, cancellationToken); | ||||||
|         if (success != result.IsSuccess) |  | ||||||
|         { |  | ||||||
|             if (!result.IsSuccess) |  | ||||||
|                 LogMessage?.LogWarning(result.ToString()); |  | ||||||
|             success = result.IsSuccess; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         return result; |         static async PooledValueTask<OperResult> UpdateVarModel(TDengineDBProducer @this, IEnumerable<VariableBasicData> item, CancellationToken cancellationToken) | ||||||
|  |         { | ||||||
|  |             var result = await @this.InserableAsync(item.WhereIf(@this._driverPropertys.OnlineFilter, a => a.IsOnline == true).ToList(), cancellationToken).ConfigureAwait(false); | ||||||
|  |             if (@this.success != result.IsSuccess) | ||||||
|  |             { | ||||||
|  |                 if (!result.IsSuccess) | ||||||
|  |                     @this.LogMessage?.LogWarning(result.ToString()); | ||||||
|  |                 @this.success = result.IsSuccess; | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             return result; | ||||||
|  |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     #region 方法 |     #region 方法 | ||||||
|  |  | ||||||
|     private async ValueTask<OperResult> InserableAsync(List<VariableBasicData> dbInserts, CancellationToken cancellationToken) |     private ValueTask<OperResult> InserableAsync(List<VariableBasicData> dbInserts, CancellationToken cancellationToken) | ||||||
|     { |     { | ||||||
|         try |         return InserableAsync(this, dbInserts, cancellationToken); | ||||||
|  |  | ||||||
|  |         static async PooledValueTask<OperResult> InserableAsync(TDengineDBProducer @this, List<VariableBasicData> dbInserts, CancellationToken cancellationToken) | ||||||
|         { |         { | ||||||
|             _db.Ado.CancellationToken = cancellationToken; |             try | ||||||
|  |  | ||||||
|             if (!_driverPropertys.BigTextScriptHistoryTable.IsNullOrEmpty()) |  | ||||||
|             { |             { | ||||||
|                 var getDeviceModel = CSharpScriptEngineExtension.Do<DynamicSQLBase>(_driverPropertys.BigTextScriptHistoryTable); |                 @this._db.Ado.CancellationToken = cancellationToken; | ||||||
|                 getDeviceModel.Logger = LogMessage; |  | ||||||
|                 await getDeviceModel.DBInsertable(_db, dbInserts, cancellationToken).ConfigureAwait(false); |                 if (!@this._driverPropertys.BigTextScriptHistoryTable.IsNullOrEmpty()) | ||||||
|  |                 { | ||||||
|  |                     var getDeviceModel = CSharpScriptEngineExtension.Do<DynamicSQLBase>(@this._driverPropertys.BigTextScriptHistoryTable); | ||||||
|  |                     getDeviceModel.Logger = @this.LogMessage; | ||||||
|  |                     await getDeviceModel.DBInsertable(@this._db, dbInserts, cancellationToken).ConfigureAwait(false); | ||||||
|  |                 } | ||||||
|  |                 else | ||||||
|  |                 { | ||||||
|  |                     var stringData = dbInserts.Where(a => (!a.IsNumber && a.Value is not bool)); | ||||||
|  |                     var numberData = dbInserts.Where(a => (a.IsNumber || a.Value is bool)); | ||||||
|  |  | ||||||
|  |                     await @this.InserableAsync(numberData, @this._driverPropertys.NumberTableNameLow, cancellationToken).ConfigureAwait(false); | ||||||
|  |  | ||||||
|  |                     await @this.InserableAsync(stringData, @this._driverPropertys.StringTableNameLow, cancellationToken).ConfigureAwait(false); | ||||||
|  |                 } | ||||||
|  |                 return OperResult.Success; | ||||||
|             } |             } | ||||||
|             else |             catch (Exception ex) | ||||||
|             { |             { | ||||||
|                 var stringData = dbInserts.Where(a => (!a.IsNumber && a.Value is not bool)); |                 return new OperResult(ex); | ||||||
|                 var numberData = dbInserts.Where(a => (a.IsNumber || a.Value is bool)); |  | ||||||
|  |  | ||||||
|                 await InserableAsync(numberData, _driverPropertys.NumberTableNameLow, cancellationToken).ConfigureAwait(false); |  | ||||||
|  |  | ||||||
|                 await InserableAsync(stringData, _driverPropertys.StringTableNameLow, cancellationToken).ConfigureAwait(false); |  | ||||||
|             } |             } | ||||||
|             return OperResult.Success; |  | ||||||
|         } |  | ||||||
|         catch (Exception ex) |  | ||||||
|         { |  | ||||||
|             return new OperResult(ex); |  | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private async Task InserableAsync(IEnumerable<VariableBasicData> dbInserts, string tableName, CancellationToken cancellationToken) |     private Task InserableAsync(IEnumerable<VariableBasicData> dbInserts, string tableName, CancellationToken cancellationToken) | ||||||
|     { |     { | ||||||
|  |         return InserableAsync(this, dbInserts, tableName, cancellationToken); | ||||||
|  |  | ||||||
|         Stopwatch stopwatch = new(); |  | ||||||
|         stopwatch.Start(); |  | ||||||
|  |  | ||||||
|         StringBuilder stringBuilder = new(); |         static async PooledTask InserableAsync(TDengineDBProducer @this, IEnumerable<VariableBasicData> dbInserts, string tableName, CancellationToken cancellationToken) | ||||||
|         stringBuilder.Append($"INSERT INTO"); |  | ||||||
|         bool any = false; |  | ||||||
|         //(`id`,`createtime`,`collecttime`,`isonline`,`value`)  |  | ||||||
|         foreach (var deviceGroup in dbInserts.GroupBy(a => a.DeviceName)) |  | ||||||
|         { |         { | ||||||
|             foreach (var variableGroup in deviceGroup.GroupBy(a => a.Name)) |             Stopwatch stopwatch = new(); | ||||||
|  |             stopwatch.Start(); | ||||||
|  |  | ||||||
|  |             StringBuilder stringBuilder = new(); | ||||||
|  |             stringBuilder.Append($"INSERT INTO"); | ||||||
|  |             bool any = false; | ||||||
|  |             //(`id`,`createtime`,`collecttime`,`isonline`,`value`)  | ||||||
|  |             foreach (var deviceGroup in dbInserts.GroupBy(a => a.DeviceName)) | ||||||
|             { |             { | ||||||
|                 any = true; |                 foreach (var variableGroup in deviceGroup.GroupBy(a => a.Name)) | ||||||
|                 stringBuilder.Append($""" |                 { | ||||||
|  |                     any = true; | ||||||
|  |                     stringBuilder.Append($""" | ||||||
|  |  | ||||||
|                      `{tableName}_{deviceGroup.Key}_{variableGroup.Key}`  |                      `{tableName}_{deviceGroup.Key}_{variableGroup.Key}`  | ||||||
|                      USING `{tableName}` TAGS ("{deviceGroup.Key}", "{variableGroup.Key}")  |                      USING `{tableName}` TAGS ("{deviceGroup.Key}", "{variableGroup.Key}")  | ||||||
| @@ -152,25 +168,28 @@ public partial class TDengineDBProducer : BusinessBaseWithCacheIntervalVariable | |||||||
|  |  | ||||||
|                     """); |                     """); | ||||||
|  |  | ||||||
|                 foreach (var item in variableGroup) |                     foreach (var item in variableGroup) | ||||||
|                 { |                     { | ||||||
|                     stringBuilder.Append($"""(NOW,"{item.CollectTime.ToString("yyyy-MM-dd HH:mm:ss.fff")}",{item.Id},{item.IsOnline},"{JsonElementExtensions.GetValue(item.Value, true)}"),"""); |                         stringBuilder.Append($"""(NOW,"{item.CollectTime.ToString("yyyy-MM-dd HH:mm:ss.fff")}",{item.Id},{item.IsOnline},"{JsonElementExtensions.GetValue(item.Value, true)}"),"""); | ||||||
|  |                     } | ||||||
|  |                     stringBuilder.Remove(stringBuilder.Length - 1, 1); | ||||||
|                 } |                 } | ||||||
|                 stringBuilder.Remove(stringBuilder.Length - 1, 1); |  | ||||||
|             } |             } | ||||||
|         } |  | ||||||
|  |  | ||||||
|         if (!any) return; |             if (!any) return; | ||||||
|  |  | ||||||
|         stringBuilder.Append(';'); |             stringBuilder.Append(';'); | ||||||
|         stringBuilder.AppendLine(); |             stringBuilder.AppendLine(); | ||||||
|  |  | ||||||
|         var result = await _db.Ado.ExecuteCommandAsync(stringBuilder.ToString(), default, cancellationToken: cancellationToken).ConfigureAwait(false); |             var result = await @this._db.Ado.ExecuteCommandAsync(stringBuilder.ToString(), default, cancellationToken: cancellationToken).ConfigureAwait(false); | ||||||
|  |  | ||||||
|         stopwatch.Stop(); |             stopwatch.Stop(); | ||||||
|         //if (result > 0) |             //if (result > 0) | ||||||
|         { |             { | ||||||
|             LogMessage?.Trace($"TableName:{tableName},Count:{result},watchTime:  {stopwatch.ElapsedMilliseconds} ms"); |                 @this.LogMessage?.Trace($"TableName:{tableName},Count:{result},watchTime:  {stopwatch.ElapsedMilliseconds} ms"); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             return; | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|     #endregion 方法 |     #endregion 方法 | ||||||
|   | |||||||
| @@ -10,6 +10,8 @@ | |||||||
|  |  | ||||||
| using BootstrapBlazor.Components; | using BootstrapBlazor.Components; | ||||||
|  |  | ||||||
|  | using PooledAwait; | ||||||
|  |  | ||||||
| using ThingsGateway.Extension.Generic; | using ThingsGateway.Extension.Generic; | ||||||
| using ThingsGateway.Foundation; | using ThingsGateway.Foundation; | ||||||
|  |  | ||||||
| @@ -148,70 +150,80 @@ public partial class Webhook : BusinessBaseWithCacheIntervalScriptAll | |||||||
|  |  | ||||||
|     private readonly HttpClient client = new HttpClient(); |     private readonly HttpClient client = new HttpClient(); | ||||||
|  |  | ||||||
|     private async Task<OperResult> WebhookUpAsync(TopicArray topicArray, CancellationToken cancellationToken) |     private Task<OperResult> WebhookUpAsync(TopicArray topicArray, CancellationToken cancellationToken) | ||||||
|     { |     { | ||||||
|         // 设置请求内容 |         return WebhookUpAsync(this, topicArray, cancellationToken); | ||||||
|         //var content = new StringContent(json, Encoding.UTF8, "application/json"); |  | ||||||
|         using var content = new ByteArrayContent(topicArray.Payload); |  | ||||||
|         content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json"); |  | ||||||
|  |  | ||||||
|         try |         static async PooledTask<OperResult> WebhookUpAsync(Webhook @this, TopicArray topicArray, CancellationToken cancellationToken) | ||||||
|         { |         { | ||||||
|             // 发送POST请求 |             // 设置请求内容 | ||||||
|             HttpResponseMessage response = await client.PostAsync(topicArray.Topic, content, cancellationToken).ConfigureAwait(false); |             //var content = new StringContent(json, Encoding.UTF8, "application/json"); | ||||||
|  |             var content = new ByteArrayContent(topicArray.Payload); | ||||||
|  |             content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json"); | ||||||
|  |  | ||||||
|             // 检查响应状态 |             try | ||||||
|             if (response.IsSuccessStatusCode) |  | ||||||
|             { |             { | ||||||
|                 if (_driverPropertys.DetailLog) |                 // 发送POST请求 | ||||||
|  |                 HttpResponseMessage response = await @this.client.PostAsync(topicArray.Topic, content, cancellationToken).ConfigureAwait(false); | ||||||
|  |  | ||||||
|  |                 // 检查响应状态 | ||||||
|  |                 if (response.IsSuccessStatusCode) | ||||||
|                 { |                 { | ||||||
|                     if (LogMessage?.LogLevel <= TouchSocket.Core.LogLevel.Trace) |                     if (@this._driverPropertys.DetailLog) | ||||||
|                         LogMessage?.LogTrace(GetDetailLogString(topicArray, _memoryVarModelQueue.Count)); |                     { | ||||||
|                     else if (LogMessage?.LogLevel <= TouchSocket.Core.LogLevel.Debug) |                         if (@this.LogMessage?.LogLevel <= TouchSocket.Core.LogLevel.Trace) | ||||||
|                         LogMessage?.LogDebug(GetCountLogString(topicArray, _memoryVarModelQueue.Count)); |                             @this.LogMessage?.LogTrace(@this.GetDetailLogString(topicArray, @this._memoryVarModelQueue.Count)); | ||||||
|  |                         else if (@this.LogMessage?.LogLevel <= TouchSocket.Core.LogLevel.Debug) | ||||||
|  |                             @this.LogMessage?.LogDebug(@this.GetCountLogString(topicArray, @this._memoryVarModelQueue.Count)); | ||||||
|  |                     } | ||||||
|  |                     else | ||||||
|  |                     { | ||||||
|  |                         if (@this.LogMessage?.LogLevel <= TouchSocket.Core.LogLevel.Debug) | ||||||
|  |                             @this.LogMessage?.LogDebug(@this.GetCountLogString(topicArray, @this._memoryVarModelQueue.Count)); | ||||||
|  |                     } | ||||||
|  |                     return new(); | ||||||
|                 } |                 } | ||||||
|                 else |                 else | ||||||
|                 { |                 { | ||||||
|                     if (LogMessage?.LogLevel <= TouchSocket.Core.LogLevel.Debug) |                     return new($"Failed to trigger webhook. Status code: {response.StatusCode}"); | ||||||
|                         LogMessage?.LogDebug(GetCountLogString(topicArray, _memoryVarModelQueue.Count)); |  | ||||||
|                 } |                 } | ||||||
|                 return new(); |  | ||||||
|             } |             } | ||||||
|             else |             catch (Exception ex) | ||||||
|             { |             { | ||||||
|                 return new($"Failed to trigger webhook. Status code: {response.StatusCode}"); |                 return new(ex); | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|         catch (Exception ex) |  | ||||||
|         { |  | ||||||
|             return new(ex); |  | ||||||
|         } |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     #region private |     #region private | ||||||
|  |  | ||||||
|     private async ValueTask<OperResult> Update(IEnumerable<TopicArray> topicArrayList, CancellationToken cancellationToken) |     private ValueTask<OperResult> Update(IEnumerable<TopicArray> topicArrayList, CancellationToken cancellationToken) | ||||||
|     { |     { | ||||||
|         foreach (var topicArray in topicArrayList) |         return Update(this, topicArrayList, cancellationToken); | ||||||
|         { |  | ||||||
|             var result = await WebhookUpAsync(topicArray, cancellationToken).ConfigureAwait(false); |  | ||||||
|  |  | ||||||
|             if (cancellationToken.IsCancellationRequested) |         static async PooledValueTask<OperResult> Update(Webhook @this, IEnumerable<TopicArray> topicArrayList, CancellationToken cancellationToken) | ||||||
|                 return result; |         { | ||||||
|             if (success != result.IsSuccess) |             foreach (var topicArray in topicArrayList) | ||||||
|             { |             { | ||||||
|  |                 var result = await @this.WebhookUpAsync(topicArray, cancellationToken).ConfigureAwait(false); | ||||||
|  |  | ||||||
|  |                 if (cancellationToken.IsCancellationRequested) | ||||||
|  |                     return result; | ||||||
|  |                 if (@this.success != result.IsSuccess) | ||||||
|  |                 { | ||||||
|  |                     if (!result.IsSuccess) | ||||||
|  |                     { | ||||||
|  |                         @this.LogMessage?.LogWarning(result.ToString()); | ||||||
|  |                     } | ||||||
|  |                     @this.success = result.IsSuccess; | ||||||
|  |                 } | ||||||
|                 if (!result.IsSuccess) |                 if (!result.IsSuccess) | ||||||
|                 { |                 { | ||||||
|                     LogMessage?.LogWarning(result.ToString()); |                     return result; | ||||||
|                 } |                 } | ||||||
|                 success = result.IsSuccess; |  | ||||||
|             } |  | ||||||
|             if (!result.IsSuccess) |  | ||||||
|             { |  | ||||||
|                 return result; |  | ||||||
|             } |             } | ||||||
|  |             return OperResult.Success; | ||||||
|         } |         } | ||||||
|         return OperResult.Success; |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |  | ||||||
|   | |||||||
| @@ -10,6 +10,8 @@ | |||||||
|  |  | ||||||
| using Confluent.Kafka; | using Confluent.Kafka; | ||||||
|  |  | ||||||
|  | using PooledAwait; | ||||||
|  |  | ||||||
| using ThingsGateway.Extension.Generic; | using ThingsGateway.Extension.Generic; | ||||||
| using ThingsGateway.Foundation; | using ThingsGateway.Foundation; | ||||||
| using ThingsGateway.Foundation.Extension.Generic; | using ThingsGateway.Foundation.Extension.Generic; | ||||||
| @@ -145,117 +147,128 @@ public partial class KafkaProducer : BusinessBaseWithCacheIntervalScriptAll | |||||||
|  |  | ||||||
|     #region private |     #region private | ||||||
|  |  | ||||||
|     private async ValueTask<OperResult> Update(IEnumerable<TopicArray> topicArrayList, CancellationToken cancellationToken) |     private ValueTask<OperResult> Update(IEnumerable<TopicArray> topicArrayList, CancellationToken cancellationToken) | ||||||
|     { |     { | ||||||
|         foreach (var topicArray in topicArrayList) |         return Update(this, topicArrayList, cancellationToken); | ||||||
|  |  | ||||||
|  |         static async PooledValueTask<OperResult> Update(KafkaProducer @this, IEnumerable<TopicArray> topicArrayList, CancellationToken cancellationToken) | ||||||
|         { |         { | ||||||
|             var result = await KafKaUpAsync(topicArray, cancellationToken).ConfigureAwait(false); |             foreach (var topicArray in topicArrayList) | ||||||
|             if (success != result.IsSuccess) |  | ||||||
|             { |             { | ||||||
|  |                 var result = await @this.KafKaUpAsync(topicArray, cancellationToken).ConfigureAwait(false); | ||||||
|  |                 if (@this.success != result.IsSuccess) | ||||||
|  |                 { | ||||||
|  |                     if (!result.IsSuccess) | ||||||
|  |                     { | ||||||
|  |                         @this.LogMessage?.LogWarning(result.ToString()); | ||||||
|  |                     } | ||||||
|  |                     @this.success = result.IsSuccess; | ||||||
|  |                 } | ||||||
|                 if (!result.IsSuccess) |                 if (!result.IsSuccess) | ||||||
|                 { |                 { | ||||||
|                     LogMessage?.LogWarning(result.ToString()); |                     return result; | ||||||
|                 } |                 } | ||||||
|                 success = result.IsSuccess; |  | ||||||
|             } |  | ||||||
|             if (!result.IsSuccess) |  | ||||||
|             { |  | ||||||
|                 return result; |  | ||||||
|             } |             } | ||||||
|  |             return OperResult.Success; | ||||||
|         } |         } | ||||||
|         return OperResult.Success; |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private async ValueTask<OperResult> UpdateAlarmModel(IEnumerable<AlarmVariable> item, CancellationToken cancellationToken) |     private ValueTask<OperResult> UpdateAlarmModel(IEnumerable<AlarmVariable> item, CancellationToken cancellationToken) | ||||||
|     { |     { | ||||||
|         var topicArrayList = GetAlarmTopicArrays(item); |         var topicArrayList = GetAlarmTopicArrays(item); | ||||||
|         return await Update(topicArrayList, cancellationToken).ConfigureAwait(false); |         return Update(topicArrayList, cancellationToken); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private async ValueTask<OperResult> UpdateDevModel(IEnumerable<DeviceBasicData> item, CancellationToken cancellationToken) |     private ValueTask<OperResult> UpdateDevModel(IEnumerable<DeviceBasicData> item, CancellationToken cancellationToken) | ||||||
|     { |     { | ||||||
|         var topicArrayList = GetDeviceTopicArray(item); |         var topicArrayList = GetDeviceTopicArray(item); | ||||||
|         return await Update(topicArrayList, cancellationToken).ConfigureAwait(false); |         return Update(topicArrayList, cancellationToken); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private async ValueTask<OperResult> UpdateVarModel(IEnumerable<VariableBasicData> item, CancellationToken cancellationToken) |     private ValueTask<OperResult> UpdateVarModel(IEnumerable<VariableBasicData> item, CancellationToken cancellationToken) | ||||||
|     { |     { | ||||||
|         var topicArrayList = GetVariableBasicDataTopicArray(item.WhereIf(_driverPropertys.OnlineFilter, a => a.IsOnline == true)); |         var topicArrayList = GetVariableBasicDataTopicArray(item.WhereIf(_driverPropertys.OnlineFilter, a => a.IsOnline == true)); | ||||||
|         return await Update(topicArrayList, cancellationToken).ConfigureAwait(false); |         return Update(topicArrayList, cancellationToken); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     #endregion private |     #endregion private | ||||||
|  |  | ||||||
|     #region 方法 |     #region 方法 | ||||||
|  |  | ||||||
|  |  | ||||||
|     private async Task AllPublishAsync(CancellationToken cancellationToken) |     private async Task AllPublishAsync(CancellationToken cancellationToken) | ||||||
|     { |     { | ||||||
|         //保留消息 |         //保留消息 | ||||||
|         //分解List,避免超出字节大小限制 |         //分解List,避免超出字节大小限制 | ||||||
|         var varData = IdVariableRuntimes.Select(a => a.Value).AdaptIEnumerableVariableBasicData().ChunkBetter(_driverPropertys.SplitSize); |         var varData = this.IdVariableRuntimes.Select(a => a.Value).AdaptIEnumerableVariableBasicData().ChunkBetter(this._driverPropertys.SplitSize); | ||||||
|         var devData = CollectDevices?.Select(a => a.Value).AdaptIEnumerableDeviceBasicData().ChunkBetter(_driverPropertys.SplitSize); |         var devData = this.CollectDevices?.Select(a => a.Value).AdaptIEnumerableDeviceBasicData().ChunkBetter(this._driverPropertys.SplitSize); | ||||||
|         var alramData = GlobalData.ReadOnlyRealAlarmIdVariables.Select(a => a.Value).ChunkBetter(_driverPropertys.SplitSize); |         var alramData = GlobalData.ReadOnlyRealAlarmIdVariables.Select(a => a.Value).ChunkBetter(this._driverPropertys.SplitSize); | ||||||
|         foreach (var item in varData) |         foreach (var item in varData) | ||||||
|         { |         { | ||||||
|             if (!success) |             if (!this.success) | ||||||
|                 break; |                 break; | ||||||
|             await UpdateVarModel(item, cancellationToken).ConfigureAwait(false); |             await this.UpdateVarModel(item, cancellationToken).ConfigureAwait(false); | ||||||
|         } |         } | ||||||
|         if (devData != null) |         if (devData != null) | ||||||
|         { |         { | ||||||
|             foreach (var item in devData) |             foreach (var item in devData) | ||||||
|             { |             { | ||||||
|                 if (!success) |                 if (!this.success) | ||||||
|                     break; |                     break; | ||||||
|                 await UpdateDevModel(item, cancellationToken).ConfigureAwait(false); |                 await this.UpdateDevModel(item, cancellationToken).ConfigureAwait(false); | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|         foreach (var item in alramData) |         foreach (var item in alramData) | ||||||
|         { |         { | ||||||
|             if (!success) |             if (!this.success) | ||||||
|                 break; |                 break; | ||||||
|             await UpdateAlarmModel(item, cancellationToken).ConfigureAwait(false); |             await this.UpdateAlarmModel(item, cancellationToken).ConfigureAwait(false); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// kafka上传,返回上传结果 |     /// kafka上传,返回上传结果 | ||||||
|     /// </summary> |     /// </summary> | ||||||
|     public async ValueTask<OperResult> KafKaUpAsync(TopicArray topicArray, CancellationToken cancellationToken) |     public ValueTask<OperResult> KafKaUpAsync(TopicArray topicArray, CancellationToken cancellationToken) | ||||||
|     { |     { | ||||||
|         try |         return KafKaUpAsync(this, topicArray, cancellationToken); | ||||||
|  |  | ||||||
|  |         static async PooledValueTask<OperResult> KafKaUpAsync(KafkaProducer @this, TopicArray topicArray, CancellationToken cancellationToken) | ||||||
|         { |         { | ||||||
|             using CancellationTokenSource cancellationTokenSource = new(_driverPropertys.Timeout); |             try | ||||||
|             using CancellationTokenSource stoppingToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationTokenSource.Token, cancellationToken); |  | ||||||
|             var result = await _producer.ProduceAsync(topicArray.Topic, new Message<Null, byte[]> { Value = topicArray.Payload }, stoppingToken.Token).ConfigureAwait(false); |  | ||||||
|             if (result.Status != PersistenceStatus.Persisted) |  | ||||||
|             { |             { | ||||||
|                 return new OperResult("Upload fail"); |                 using CancellationTokenSource cancellationTokenSource = new(@this._driverPropertys.Timeout); | ||||||
|             } |                 using CancellationTokenSource stoppingToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationTokenSource.Token, cancellationToken); | ||||||
|             else |                 var result = await @this._producer.ProduceAsync(topicArray.Topic, new Message<Null, byte[]> { Value = topicArray.Payload }, stoppingToken.Token).ConfigureAwait(false); | ||||||
|             { |                 if (result.Status != PersistenceStatus.Persisted) | ||||||
|                 if (_driverPropertys.DetailLog) |  | ||||||
|                 { |                 { | ||||||
|                     if (LogMessage?.LogLevel <= TouchSocket.Core.LogLevel.Trace) |                     return new OperResult("Upload fail"); | ||||||
|                         LogMessage?.LogTrace(GetDetailLogString(topicArray, _memoryVarModelQueue.Count)); |  | ||||||
|                     else if (LogMessage?.LogLevel <= TouchSocket.Core.LogLevel.Debug) |  | ||||||
|                         LogMessage?.LogDebug(GetCountLogString(topicArray, _memoryVarModelQueue.Count)); |  | ||||||
|                 } |                 } | ||||||
|                 else |                 else | ||||||
|                 { |                 { | ||||||
|                     if (LogMessage?.LogLevel <= TouchSocket.Core.LogLevel.Debug) |                     if (@this._driverPropertys.DetailLog) | ||||||
|                         LogMessage?.LogDebug(GetCountLogString(topicArray, _memoryVarModelQueue.Count)); |                     { | ||||||
|  |                         if (@this.LogMessage?.LogLevel <= TouchSocket.Core.LogLevel.Trace) | ||||||
|  |                             @this.LogMessage?.LogTrace(@this.GetDetailLogString(topicArray, @this._memoryVarModelQueue.Count)); | ||||||
|  |                         else if (@this.LogMessage?.LogLevel <= TouchSocket.Core.LogLevel.Debug) | ||||||
|  |                             @this.LogMessage?.LogDebug(@this.GetCountLogString(topicArray, @this._memoryVarModelQueue.Count)); | ||||||
|  |                     } | ||||||
|  |                     else | ||||||
|  |                     { | ||||||
|  |                         if (@this.LogMessage?.LogLevel <= TouchSocket.Core.LogLevel.Debug) | ||||||
|  |                             @this.LogMessage?.LogDebug(@this.GetCountLogString(topicArray, @this._memoryVarModelQueue.Count)); | ||||||
|  |                     } | ||||||
|  |                     return OperResult.Success; | ||||||
|                 } |                 } | ||||||
|                 return OperResult.Success; |  | ||||||
|             } |             } | ||||||
|         } |             catch (OperationCanceledException) | ||||||
|         catch (OperationCanceledException) |             { | ||||||
|         { |                 return new OperResult("Timeout"); | ||||||
|             return new OperResult("Timeout"); |             } | ||||||
|         } |             catch (Exception ex) | ||||||
|         catch (Exception ex) |             { | ||||||
|         { |                 return new OperResult(ex); | ||||||
|             return new OperResult(ex); |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -10,6 +10,8 @@ | |||||||
|  |  | ||||||
| using Microsoft.Extensions.Localization; | using Microsoft.Extensions.Localization; | ||||||
|  |  | ||||||
|  | using PooledAwait; | ||||||
|  |  | ||||||
| using System.Collections.Concurrent; | using System.Collections.Concurrent; | ||||||
|  |  | ||||||
| using ThingsGateway.Foundation.Modbus; | using ThingsGateway.Foundation.Modbus; | ||||||
| @@ -151,95 +153,108 @@ public class ModbusSlave : BusinessBase | |||||||
|         await base.DisposeAsync(disposing).ConfigureAwait(false); |         await base.DisposeAsync(disposing).ConfigureAwait(false); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     protected override async Task ProtectedExecuteAsync(object? state, CancellationToken cancellationToken) |     protected override Task ProtectedExecuteAsync(object? state, CancellationToken cancellationToken) | ||||||
|     { |     { | ||||||
|         //获取设备连接状态 |         return ProtectedExecuteAsync(this, cancellationToken); | ||||||
|         if (!IsConnected()) |  | ||||||
|  |  | ||||||
|  |         static async PooledTask ProtectedExecuteAsync(ModbusSlave @this, CancellationToken cancellationToken) | ||||||
|         { |         { | ||||||
|             try |             //获取设备连接状态 | ||||||
|  |             if (!@this.IsConnected()) | ||||||
|  |             { | ||||||
|  |                 try | ||||||
|  |                 { | ||||||
|  |                     if (cancellationToken.IsCancellationRequested) | ||||||
|  |                         return; | ||||||
|  |                     await @this._plc.ConnectAsync(cancellationToken).ConfigureAwait(false); | ||||||
|  |                     @this.success = true; | ||||||
|  |                 } | ||||||
|  |                 catch (ObjectDisposedException) { } | ||||||
|  |                 catch (Exception ex) | ||||||
|  |                 { | ||||||
|  |                     if (@this.success) | ||||||
|  |                         @this.LogMessage?.LogWarning(ex, "Failed to start service"); | ||||||
|  |                     @this.success = false; | ||||||
|  |                     await Task.Delay(10000, cancellationToken).ConfigureAwait(false); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             var list = @this.ModbusVariableQueue.ToDictWithDequeue(); | ||||||
|  |             foreach (var item in list) | ||||||
|             { |             { | ||||||
|                 if (cancellationToken.IsCancellationRequested) |                 if (cancellationToken.IsCancellationRequested) | ||||||
|                     return; |                     break; | ||||||
|                 await _plc.ConnectAsync(cancellationToken).ConfigureAwait(false); |                 if (!@this.IdVariableRuntimes.TryGetValue(item.Value, out var variableRuntime)) | ||||||
|                 success = true; |                     break; | ||||||
|             } |  | ||||||
|             catch (ObjectDisposedException) { } |  | ||||||
|             catch (Exception ex) |  | ||||||
|             { |  | ||||||
|                 if (success) |  | ||||||
|                     LogMessage?.LogWarning(ex, "Failed to start service"); |  | ||||||
|                 success = false; |  | ||||||
|                 await Task.Delay(10000, cancellationToken).ConfigureAwait(false); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|         var list = ModbusVariableQueue.ToDictWithDequeue(); |  | ||||||
|         foreach (var item in list) |  | ||||||
|         { |  | ||||||
|             if (cancellationToken.IsCancellationRequested) |  | ||||||
|                 break; |  | ||||||
|             if (!IdVariableRuntimes.TryGetValue(item.Value, out var variableRuntime)) |  | ||||||
|                 break; |  | ||||||
|  |  | ||||||
|             var type = variableRuntime.GetPropertyValue(CurrentDevice.Id, nameof(ModbusSlaveVariableProperty.DataType)); |                 var type = variableRuntime.GetPropertyValue(@this.CurrentDevice.Id, nameof(ModbusSlaveVariableProperty.DataType)); | ||||||
|             if (Enum.TryParse(type, out DataTypeEnum result)) |                 if (Enum.TryParse(type, out DataTypeEnum result)) | ||||||
|             { |                 { | ||||||
|                 await _plc.WriteJTokenAsync(item.Key, (variableRuntime.Value).GetJTokenFromObj(), result, cancellationToken).ConfigureAwait(false); |                     await @this._plc.WriteJTokenAsync(item.Key, (variableRuntime.Value).GetJTokenFromObj(), result, cancellationToken).ConfigureAwait(false); | ||||||
|             } |                 } | ||||||
|             else |                 else | ||||||
|             { |                 { | ||||||
|                 await _plc.WriteJTokenAsync(item.Key, (variableRuntime.Value).GetJTokenFromObj(), variableRuntime.DataType, cancellationToken).ConfigureAwait(false); |                     await @this._plc.WriteJTokenAsync(item.Key, (variableRuntime.Value).GetJTokenFromObj(), variableRuntime.DataType, cancellationToken).ConfigureAwait(false); | ||||||
|  |                 } | ||||||
|             } |             } | ||||||
|  |  | ||||||
|  |             return; | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// RPC写入 |     /// RPC写入 | ||||||
|     /// </summary> |     /// </summary> | ||||||
|     private async ValueTask<IOperResult> OnWriteData(ModbusRequest modbusRequest, IThingsGatewayBitConverter bitConverter, IChannel channel) |     private ValueTask<IOperResult> OnWriteData(ModbusRequest modbusRequest, IThingsGatewayBitConverter bitConverter, IChannel channel) | ||||||
|     { |     { | ||||||
|         try |         return OnWriteData(this, modbusRequest, bitConverter, channel); | ||||||
|  |  | ||||||
|  |         static async PooledValueTask<IOperResult> OnWriteData(ModbusSlave @this, ModbusRequest modbusRequest, IThingsGatewayBitConverter bitConverter, IChannel channel) | ||||||
|         { |         { | ||||||
|             var tag = ModbusVariables.Where(a => a.Key?.StartAddress == modbusRequest.StartAddress && a.Key?.Station == modbusRequest.Station && a.Key?.FunctionCode == modbusRequest.FunctionCode).ToArray(); |             try | ||||||
|             if (tag.Length == 0) return OperResult.Success; |  | ||||||
|  |  | ||||||
|  |  | ||||||
|             foreach (var item in tag) |  | ||||||
|             { |             { | ||||||
|                 if (!(item.Value.GetPropertyValue(DeviceId, nameof(_variablePropertys.VariableRpcEnable)).ToBoolean(false) && _driverPropertys.DeviceRpcEnable)) |                 var tag = @this.ModbusVariables.Where(a => a.Key?.StartAddress == modbusRequest.StartAddress && a.Key?.Station == modbusRequest.Station && a.Key?.FunctionCode == modbusRequest.FunctionCode).ToArray(); | ||||||
|                     return new OperResult("Not Permitted to Write"); |                 if (tag.Length == 0) return OperResult.Success; | ||||||
|  |  | ||||||
|                 var type = item.Value.GetPropertyValue(DeviceId, nameof(ModbusSlaveVariableProperty.DataType)); |  | ||||||
|                 var dType = Enum.TryParse(type, out DataTypeEnum dataType) ? dataType : item.Value.DataType; |  | ||||||
|                 var addressStr = item.Value.GetPropertyValue(DeviceId, nameof(ModbusSlaveVariableProperty.ServiceAddress)); |  | ||||||
|  |  | ||||||
|                 var thingsGatewayBitConverter = bitConverter.GetTransByAddress(addressStr); |                 foreach (var item in tag) | ||||||
|  |  | ||||||
|                 var bitIndex = _plc.GetBitOffset(addressStr); |  | ||||||
|                 if (modbusRequest.FunctionCode == 0x03 && dType == DataTypeEnum.Boolean && bitIndex != null) |  | ||||||
|                 { |                 { | ||||||
|                     var int16Data = thingsGatewayBitConverter.ToUInt16(modbusRequest.MasterWriteDatas.Span, 0); |                     if (!(item.Value.GetPropertyValue(@this.DeviceId, nameof(_variablePropertys.VariableRpcEnable)).ToBoolean(false) && @this._driverPropertys.DeviceRpcEnable)) | ||||||
|                     var wData = BitHelper.GetBit(int16Data, bitIndex.Value); |                         return new OperResult("Not Permitted to Write"); | ||||||
|  |  | ||||||
|                     var result = await item.Value.RpcAsync(wData.ToSystemTextJsonString(), $"{nameof(ModbusSlave)}-{CurrentDevice.Name}-{$"{channel}"}").ConfigureAwait(false); |                     var type = item.Value.GetPropertyValue(@this.DeviceId, nameof(ModbusSlaveVariableProperty.DataType)); | ||||||
|  |                     var dType = Enum.TryParse(type, out DataTypeEnum dataType) ? dataType : item.Value.DataType; | ||||||
|  |                     var addressStr = item.Value.GetPropertyValue(@this.DeviceId, nameof(ModbusSlaveVariableProperty.ServiceAddress)); | ||||||
|  |  | ||||||
|                     if (!result.IsSuccess) |                     var thingsGatewayBitConverter = bitConverter.GetTransByAddress(addressStr); | ||||||
|                         return result; |  | ||||||
|                 } |                     var bitIndex = @this._plc.GetBitOffset(addressStr); | ||||||
|                 else |                     if (modbusRequest.FunctionCode == 0x03 && dType == DataTypeEnum.Boolean && bitIndex != null) | ||||||
|                 { |                     { | ||||||
|                     _ = thingsGatewayBitConverter.GetChangedDataFormBytes(_plc, addressStr, modbusRequest.MasterWriteDatas.Span, 0, dType, item.Value.ArrayLength ?? 1, null, out var data); |                         var int16Data = thingsGatewayBitConverter.ToUInt16(modbusRequest.MasterWriteDatas.Span, 0); | ||||||
|  |                         var wData = BitHelper.GetBit(int16Data, bitIndex.Value); | ||||||
|                     var result = await item.Value.RpcAsync(data.ToSystemTextJsonString(), $"{nameof(ModbusSlave)}-{CurrentDevice.Name}-{$"{channel}"}").ConfigureAwait(false); |  | ||||||
|  |                         var result = await item.Value.RpcAsync(wData.ToSystemTextJsonString(), $"{nameof(ModbusSlave)}-{@this.CurrentDevice.Name}-{$"{channel}"}").ConfigureAwait(false); | ||||||
|                     if (!result.IsSuccess) |  | ||||||
|                         return result; |                         if (!result.IsSuccess) | ||||||
|  |                             return result; | ||||||
|  |                     } | ||||||
|  |                     else | ||||||
|  |                     { | ||||||
|  |                         _ = thingsGatewayBitConverter.GetChangedDataFormBytes(@this._plc, addressStr, modbusRequest.MasterWriteDatas.Span, 0, dType, item.Value.ArrayLength ?? 1, null, out var data); | ||||||
|  |  | ||||||
|  |                         var result = await item.Value.RpcAsync(data.ToSystemTextJsonString(), $"{nameof(ModbusSlave)}-{@this.CurrentDevice.Name}-{$"{channel}"}").ConfigureAwait(false); | ||||||
|  |  | ||||||
|  |                         if (!result.IsSuccess) | ||||||
|  |                             return result; | ||||||
|  |                     } | ||||||
|                 } |                 } | ||||||
|  |                 return OperResult.Success; | ||||||
|  |             } | ||||||
|  |             catch (Exception ex) | ||||||
|  |             { | ||||||
|  |                 return new OperResult(ex); | ||||||
|             } |             } | ||||||
|             return OperResult.Success; |  | ||||||
|         } |  | ||||||
|         catch (Exception ex) |  | ||||||
|         { |  | ||||||
|             return new OperResult(ex); |  | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -10,6 +10,9 @@ | |||||||
|  |  | ||||||
| using MQTTnet; | using MQTTnet; | ||||||
|  |  | ||||||
|  | using PooledAwait; | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -202,31 +205,38 @@ public partial class MqttClient : BusinessBaseWithCacheIntervalScriptAll | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     protected override async Task ProtectedExecuteAsync(object? state, CancellationToken cancellationToken) |     protected override Task ProtectedExecuteAsync(object? state, CancellationToken cancellationToken) | ||||||
|     { |     { | ||||||
|         var clientResult = await TryMqttClientAsync(cancellationToken).ConfigureAwait(false); |         return ProtectedExecuteAsync(this, cancellationToken); | ||||||
|         if (!clientResult.IsSuccess) |  | ||||||
|  |  | ||||||
|  |         static async PooledTask ProtectedExecuteAsync(MqttClient @this, CancellationToken cancellationToken) | ||||||
|         { |         { | ||||||
|             if (cancellationToken.IsCancellationRequested) |             var clientResult = await @this.TryMqttClientAsync(cancellationToken).ConfigureAwait(false); | ||||||
|                 return; |             if (!clientResult.IsSuccess) | ||||||
|             if (success != clientResult.IsSuccess) |  | ||||||
|             { |             { | ||||||
|                 if (!clientResult.IsSuccess) |                 if (cancellationToken.IsCancellationRequested) | ||||||
|                     LogMessage?.LogWarning(clientResult.Exception, clientResult.ErrorMessage); |                     return; | ||||||
|                 success = clientResult.IsSuccess; |                 if (@this.success != clientResult.IsSuccess) | ||||||
|  |                 { | ||||||
|  |                     if (!clientResult.IsSuccess) | ||||||
|  |                         @this.LogMessage?.LogWarning(clientResult.Exception, clientResult.ErrorMessage); | ||||||
|  |                     @this.success = clientResult.IsSuccess; | ||||||
|  |                 } | ||||||
|  |                 await Task.Delay(10000, cancellationToken).ConfigureAwait(false); | ||||||
|  |                 //return; | ||||||
|             } |             } | ||||||
|             await Task.Delay(10000, cancellationToken).ConfigureAwait(false); |             //TD设备上线 | ||||||
|             //return; |  | ||||||
|         } |  | ||||||
|         //TD设备上线 |  | ||||||
|  |  | ||||||
|         var data = ThingsBoardDeviceConnectQueue.ToListWithDequeue(); |             var data = @this.ThingsBoardDeviceConnectQueue.ToListWithDequeue(); | ||||||
|         foreach (var item in data) |             foreach (var item in data) | ||||||
|         { |             { | ||||||
|             await UpdateThingsBoardDeviceConnect(item).ConfigureAwait(false); |                 await @this.UpdateThingsBoardDeviceConnect(item).ConfigureAwait(false); | ||||||
|         } |             } | ||||||
|  |  | ||||||
|         await Update(cancellationToken).ConfigureAwait(false); |             await @this.Update(cancellationToken).ConfigureAwait(false); | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |  | ||||||
|   | |||||||
| @@ -20,6 +20,8 @@ using MQTTnet.Client; | |||||||
|  |  | ||||||
| using Newtonsoft.Json.Linq; | using Newtonsoft.Json.Linq; | ||||||
|  |  | ||||||
|  | using PooledAwait; | ||||||
|  |  | ||||||
| using System.Collections.Concurrent; | using System.Collections.Concurrent; | ||||||
| using System.Text; | using System.Text; | ||||||
|  |  | ||||||
| @@ -30,6 +32,7 @@ using ThingsGateway.NewLife; | |||||||
| using ThingsGateway.NewLife.Extension; | using ThingsGateway.NewLife.Extension; | ||||||
| using ThingsGateway.NewLife.Json.Extension; | using ThingsGateway.NewLife.Json.Extension; | ||||||
|  |  | ||||||
|  |  | ||||||
| namespace ThingsGateway.Plugin.Mqtt; | namespace ThingsGateway.Plugin.Mqtt; | ||||||
|  |  | ||||||
| /// <summary> | /// <summary> | ||||||
| @@ -222,27 +225,32 @@ public partial class MqttClient : BusinessBaseWithCacheIntervalScriptAll | |||||||
|  |  | ||||||
|     #region private |     #region private | ||||||
|  |  | ||||||
|     private async ValueTask<OperResult> Update(IEnumerable<TopicArray> topicArrayList, CancellationToken cancellationToken) |     private ValueTask<OperResult> Update(IEnumerable<TopicArray> topicArrayList, CancellationToken cancellationToken) | ||||||
|     { |     { | ||||||
|         foreach (TopicArray topicArray in topicArrayList) |         return Update(this, topicArrayList, cancellationToken); | ||||||
|  |  | ||||||
|  |         static async PooledValueTask<OperResult> Update(MqttClient @this, IEnumerable<TopicArray> topicArrayList, CancellationToken cancellationToken) | ||||||
|         { |         { | ||||||
|             var result = await MqttUpAsync(topicArray, cancellationToken).ConfigureAwait(false); |             foreach (TopicArray topicArray in topicArrayList) | ||||||
|             if (cancellationToken.IsCancellationRequested) |  | ||||||
|                 return result; |  | ||||||
|             if (success != result.IsSuccess) |  | ||||||
|             { |             { | ||||||
|  |                 var result = await @this.MqttUpAsync(topicArray, cancellationToken).ConfigureAwait(false); | ||||||
|  |                 if (cancellationToken.IsCancellationRequested) | ||||||
|  |                     return result; | ||||||
|  |                 if (@this.success != result.IsSuccess) | ||||||
|  |                 { | ||||||
|  |                     if (!result.IsSuccess) | ||||||
|  |                     { | ||||||
|  |                         @this.LogMessage?.LogWarning(result.ToString()); | ||||||
|  |                     } | ||||||
|  |                     @this.success = result.IsSuccess; | ||||||
|  |                 } | ||||||
|                 if (!result.IsSuccess) |                 if (!result.IsSuccess) | ||||||
|                 { |                 { | ||||||
|                     LogMessage?.LogWarning(result.ToString()); |                     return result; | ||||||
|                 } |                 } | ||||||
|                 success = result.IsSuccess; |  | ||||||
|             } |  | ||||||
|             if (!result.IsSuccess) |  | ||||||
|             { |  | ||||||
|                 return result; |  | ||||||
|             } |             } | ||||||
|  |             return OperResult.Success; | ||||||
|         } |         } | ||||||
|         return OperResult.Success; |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private ValueTask<OperResult> UpdateAlarmModel(IEnumerable<AlarmVariable> item, CancellationToken cancellationToken) |     private ValueTask<OperResult> UpdateAlarmModel(IEnumerable<AlarmVariable> item, CancellationToken cancellationToken) | ||||||
| @@ -265,96 +273,102 @@ public partial class MqttClient : BusinessBaseWithCacheIntervalScriptAll | |||||||
|  |  | ||||||
|     #endregion private |     #endregion private | ||||||
|  |  | ||||||
|     private async ValueTask AllPublishAsync(CancellationToken cancellationToken) |     private async Task AllPublishAsync(CancellationToken cancellationToken) | ||||||
|     { |     { | ||||||
|  |  | ||||||
|         //保留消息 |         //保留消息 | ||||||
|         //分解List,避免超出mqtt字节大小限制 |         //分解List,避免超出mqtt字节大小限制 | ||||||
|         var varData = IdVariableRuntimes.Select(a => a.Value).AdaptIEnumerableVariableBasicData().ChunkBetter(_driverPropertys.SplitSize); |         var varData = this.IdVariableRuntimes.Select(a => a.Value).AdaptIEnumerableVariableBasicData().ChunkBetter(this._driverPropertys.SplitSize); | ||||||
|         var devData = CollectDevices?.Select(a => a.Value).AdaptIEnumerableDeviceBasicData().ChunkBetter(_driverPropertys.SplitSize); |         var devData = this.CollectDevices?.Select(a => a.Value).AdaptIEnumerableDeviceBasicData().ChunkBetter(this._driverPropertys.SplitSize); | ||||||
|         var alramData = GlobalData.ReadOnlyRealAlarmIdVariables.Select(a => a.Value).ChunkBetter(_driverPropertys.SplitSize); |         var alramData = GlobalData.ReadOnlyRealAlarmIdVariables.Select(a => a.Value).ChunkBetter(this._driverPropertys.SplitSize); | ||||||
|         foreach (var item in varData) |         foreach (var item in varData) | ||||||
|         { |         { | ||||||
|             if (!success) |             if (!this.success) | ||||||
|                 break; |                 break; | ||||||
|             await UpdateVarModel(item, cancellationToken).ConfigureAwait(false); |             await this.UpdateVarModel(item, cancellationToken).ConfigureAwait(false); | ||||||
|         } |         } | ||||||
|         if (devData != null) |         if (devData != null) | ||||||
|         { |         { | ||||||
|             foreach (var item in devData) |             foreach (var item in devData) | ||||||
|             { |             { | ||||||
|                 if (!success) |                 if (!this.success) | ||||||
|                     break; |                     break; | ||||||
|                 await UpdateDevModel(item, cancellationToken).ConfigureAwait(false); |                 await this.UpdateDevModel(item, cancellationToken).ConfigureAwait(false); | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         foreach (var item in alramData) |         foreach (var item in alramData) | ||||||
|         { |         { | ||||||
|             if (!success) |             if (!this.success) | ||||||
|                 break; |                 break; | ||||||
|             await UpdateAlarmModel(item, cancellationToken).ConfigureAwait(false); |             await this.UpdateAlarmModel(item, cancellationToken).ConfigureAwait(false); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private async ValueTask<Dictionary<string, Dictionary<string, IOperResult>>> GetRpcResult(string clientId, Dictionary<string, Dictionary<string, JToken>> rpcDatas) |     private ValueTask<Dictionary<string, Dictionary<string, IOperResult>>> GetRpcResult(string clientId, Dictionary<string, Dictionary<string, JToken>> rpcDatas) | ||||||
|     { |     { | ||||||
|         var mqttRpcResult = new Dictionary<string, Dictionary<string, IOperResult>>(); |         return GetRpcResult(this, clientId, rpcDatas); | ||||||
|         rpcDatas.ForEach(a => mqttRpcResult.Add(a.Key, new())); |  | ||||||
|         try |         static async PooledValueTask<Dictionary<string, Dictionary<string, IOperResult>>> GetRpcResult(MqttClient @this, string clientId, Dictionary<string, Dictionary<string, JToken>> rpcDatas) | ||||||
|         { |         { | ||||||
|             foreach (var rpcData in rpcDatas) |             var mqttRpcResult = new Dictionary<string, Dictionary<string, IOperResult>>(); | ||||||
|  |             rpcDatas.ForEach(a => mqttRpcResult.Add(a.Key, new())); | ||||||
|  |             try | ||||||
|             { |             { | ||||||
|                 if (GlobalData.ReadOnlyDevices.TryGetValue(rpcData.Key, out var device)) |                 foreach (var rpcData in rpcDatas) | ||||||
|                 { |                 { | ||||||
|                     foreach (var item in rpcData.Value) |                     if (GlobalData.ReadOnlyDevices.TryGetValue(rpcData.Key, out var device)) | ||||||
|                     { |                     { | ||||||
|                         if (device.ReadOnlyVariableRuntimes.TryGetValue(item.Key, out var variable) && IdVariableRuntimes.TryGetValue(variable.Id, out var tag)) |                         foreach (var item in rpcData.Value) | ||||||
|                         { |                         { | ||||||
|                             var rpcEnable = tag.GetPropertyValue(DeviceId, nameof(_variablePropertys.VariableRpcEnable))?.ToBoolean(); |                             if (device.ReadOnlyVariableRuntimes.TryGetValue(item.Key, out var variable) && @this.IdVariableRuntimes.TryGetValue(variable.Id, out var tag)) | ||||||
|                             if (rpcEnable == false) |  | ||||||
|                             { |                             { | ||||||
|                                 mqttRpcResult[rpcData.Key].Add(item.Key, new OperResult("RPCEnable is False")); |                                 var rpcEnable = tag.GetPropertyValue(@this.DeviceId, nameof(_variablePropertys.VariableRpcEnable))?.ToBoolean(); | ||||||
|  |                                 if (rpcEnable == false) | ||||||
|  |                                 { | ||||||
|  |                                     mqttRpcResult[rpcData.Key].Add(item.Key, new OperResult("RPCEnable is False")); | ||||||
|  |                                 } | ||||||
|  |                             } | ||||||
|  |                             else | ||||||
|  |                             { | ||||||
|  |                                 mqttRpcResult[rpcData.Key].Add(item.Key, new OperResult("The variable does not exist")); | ||||||
|                             } |                             } | ||||||
|                         } |                         } | ||||||
|                         else |                     } | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 Dictionary<string, Dictionary<string, string>> writeData = new(); | ||||||
|  |                 foreach (var item in rpcDatas) | ||||||
|  |                 { | ||||||
|  |                     writeData.Add(item.Key, new()); | ||||||
|  |  | ||||||
|  |                     foreach (var kv in item.Value) | ||||||
|  |                     { | ||||||
|  |                         if (!mqttRpcResult[item.Key].ContainsKey(kv.Key)) | ||||||
|                         { |                         { | ||||||
|                             mqttRpcResult[rpcData.Key].Add(item.Key, new OperResult("The variable does not exist")); |                             writeData[item.Key].Add(kv.Key, kv.Value?.ToString()); | ||||||
|                         } |                         } | ||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
|             } |  | ||||||
|  |  | ||||||
|             Dictionary<string, Dictionary<string, string>> writeData = new(); |                 var result = await GlobalData.RpcService.InvokeDeviceMethodAsync(@this.ToString() + "-" + clientId, | ||||||
|             foreach (var item in rpcDatas) |                     writeData).ConfigureAwait(false); | ||||||
|             { |  | ||||||
|                 writeData.Add(item.Key, new()); |  | ||||||
|  |  | ||||||
|                 foreach (var kv in item.Value) |                 foreach (var dictKv in result) | ||||||
|                 { |                 { | ||||||
|                     if (!mqttRpcResult[item.Key].ContainsKey(kv.Key)) |                     foreach (var item in dictKv.Value) | ||||||
|                     { |                     { | ||||||
|                         writeData[item.Key].Add(kv.Key, kv.Value?.ToString()); |                         mqttRpcResult[dictKv.Key].TryAdd(item.Key, item.Value); | ||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|  |             catch (Exception ex) | ||||||
|             var result = await GlobalData.RpcService.InvokeDeviceMethodAsync(ToString() + "-" + clientId, |  | ||||||
|                 writeData).ConfigureAwait(false); |  | ||||||
|  |  | ||||||
|             foreach (var dictKv in result) |  | ||||||
|             { |             { | ||||||
|                 foreach (var item in dictKv.Value) |                 @this.LogMessage?.LogWarning(ex); | ||||||
|                 { |  | ||||||
|                     mqttRpcResult[dictKv.Key].TryAdd(item.Key, item.Value); |  | ||||||
|                 } |  | ||||||
|             } |             } | ||||||
|         } |  | ||||||
|         catch (Exception ex) |  | ||||||
|         { |  | ||||||
|             LogMessage?.LogWarning(ex); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         return mqttRpcResult; |             return mqttRpcResult; | ||||||
|  |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private async Task MqttClient_ApplicationMessageReceivedAsync(MqttApplicationMessageReceivedEventArgs args) |     private async Task MqttClient_ApplicationMessageReceivedAsync(MqttApplicationMessageReceivedEventArgs args) | ||||||
| @@ -482,77 +496,82 @@ public partial class MqttClient : BusinessBaseWithCacheIntervalScriptAll | |||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// 上传mqtt,返回上传结果 |     /// 上传mqtt,返回上传结果 | ||||||
|     /// </summary> |     /// </summary> | ||||||
|     public async ValueTask<OperResult> MqttUpAsync(TopicArray topicArray, CancellationToken cancellationToken = default) |     public ValueTask<OperResult> MqttUpAsync(TopicArray topicArray, CancellationToken cancellationToken = default) | ||||||
|     { |     { | ||||||
|         try |         return MqttUpAsync(this, topicArray, cancellationToken); | ||||||
|  |  | ||||||
|  |         static async PooledValueTask<OperResult> MqttUpAsync(MqttClient @this, TopicArray topicArray, CancellationToken cancellationToken) | ||||||
|         { |         { | ||||||
|             var isConnect = await TryMqttClientAsync(cancellationToken).ConfigureAwait(false); |             try | ||||||
|             if (isConnect.IsSuccess) |  | ||||||
|             { |             { | ||||||
|                 var variableMessage = new MqttApplicationMessageBuilder() |                 var isConnect = await @this.TryMqttClientAsync(cancellationToken).ConfigureAwait(false); | ||||||
|     .WithTopic(topicArray.Topic).WithQualityOfServiceLevel(_driverPropertys.MqttQualityOfServiceLevel).WithRetainFlag() |                 if (isConnect.IsSuccess) | ||||||
|     .WithPayload(topicArray.Payload).Build(); |  | ||||||
|                 var result = await _mqttClient.PublishAsync(variableMessage, cancellationToken).ConfigureAwait(false); |  | ||||||
|                 if (result.IsSuccess) |  | ||||||
|                 { |                 { | ||||||
|                     if (_driverPropertys.DetailLog) |                     var variableMessage = new MqttApplicationMessageBuilder() | ||||||
|  |         .WithTopic(topicArray.Topic).WithQualityOfServiceLevel(@this._driverPropertys.MqttQualityOfServiceLevel).WithRetainFlag() | ||||||
|  |         .WithPayload(topicArray.Payload).Build(); | ||||||
|  |                     var result = await @this._mqttClient.PublishAsync(variableMessage, cancellationToken).ConfigureAwait(false); | ||||||
|  |                     if (result.IsSuccess) | ||||||
|                     { |                     { | ||||||
|                         if (LogMessage?.LogLevel <= TouchSocket.Core.LogLevel.Trace) |                         if (@this._driverPropertys.DetailLog) | ||||||
|                             LogMessage?.LogTrace(GetDetailLogString(topicArray, _memoryVarModelQueue.Count)); |                         { | ||||||
|                         else if (LogMessage?.LogLevel <= TouchSocket.Core.LogLevel.Debug) |                             if (@this.LogMessage?.LogLevel <= TouchSocket.Core.LogLevel.Trace) | ||||||
|                             LogMessage?.LogDebug(GetCountLogString(topicArray, _memoryVarModelQueue.Count)); |                                 @this.LogMessage?.LogTrace(@this.GetDetailLogString(topicArray, @this._memoryVarModelQueue.Count)); | ||||||
|  |                             else if (@this.LogMessage?.LogLevel <= TouchSocket.Core.LogLevel.Debug) | ||||||
|  |                                 @this.LogMessage?.LogDebug(@this.GetCountLogString(topicArray, @this._memoryVarModelQueue.Count)); | ||||||
|  |                         } | ||||||
|  |                         else | ||||||
|  |                         { | ||||||
|  |                             if (@this.LogMessage?.LogLevel <= TouchSocket.Core.LogLevel.Debug) | ||||||
|  |                                 @this.LogMessage?.LogDebug(@this.GetCountLogString(topicArray, @this._memoryVarModelQueue.Count)); | ||||||
|  |                         } | ||||||
|  |                         return OperResult.Success; | ||||||
|                     } |                     } | ||||||
|                     else |                     else | ||||||
|                     { |                     { | ||||||
|                         if (LogMessage?.LogLevel <= TouchSocket.Core.LogLevel.Debug) |                         return new OperResult($"Upload fail{result.ReasonString}"); | ||||||
|                             LogMessage?.LogDebug(GetCountLogString(topicArray, _memoryVarModelQueue.Count)); |  | ||||||
|                     } |                     } | ||||||
|                     return OperResult.Success; |  | ||||||
|                 } |                 } | ||||||
|                 else |                 else | ||||||
|                 { |                 { | ||||||
|                     return new OperResult($"Upload fail{result.ReasonString}"); |                     return isConnect; | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|             else |             catch (Exception ex) | ||||||
|             { |             { | ||||||
|                 return isConnect; |                 return new OperResult($"Upload fail", ex); | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|         catch (Exception ex) |  | ||||||
|         { |  | ||||||
|             return new OperResult($"Upload fail", ex); |  | ||||||
|         } |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private async ValueTask<OperResult> TryMqttClientAsync(CancellationToken cancellationToken) |     private ValueTask<OperResult> TryMqttClientAsync(CancellationToken cancellationToken) | ||||||
|     { |     { | ||||||
|         if (DisposedValue || _mqttClient == null) return new OperResult("MqttClient is disposed"); |         if (DisposedValue || _mqttClient == null) return TouchSocket.Core.EasyValueTask.FromResult(new OperResult("MqttClient is disposed")); | ||||||
|  |  | ||||||
|         if (_mqttClient?.IsConnected == true) |         if (_mqttClient?.IsConnected == true) | ||||||
|             return OperResult.Success; |             return TouchSocket.Core.EasyValueTask.FromResult(OperResult.Success); | ||||||
|         return await Client().ConfigureAwait(false); |         return Client(this, cancellationToken); | ||||||
|  |  | ||||||
|         async ValueTask<OperResult> Client() |         static async PooledValueTask<OperResult> Client(MqttClient @this, CancellationToken cancellationToken) | ||||||
|         { |         { | ||||||
|             if (_mqttClient?.IsConnected == true) |             if (@this._mqttClient?.IsConnected == true) | ||||||
|                 return OperResult.Success; |                 return OperResult.Success; | ||||||
|             try |             try | ||||||
|             { |             { | ||||||
|                 await ConnectLock.WaitAsync(cancellationToken).ConfigureAwait(false); |                 await @this.ConnectLock.WaitAsync(cancellationToken).ConfigureAwait(false); | ||||||
|                 await Task.Delay(100, cancellationToken).ConfigureAwait(false); |                 await Task.Delay(100, cancellationToken).ConfigureAwait(false); | ||||||
|                 if (_mqttClient?.IsConnected == true) |                 if (@this._mqttClient?.IsConnected == true) | ||||||
|                     return OperResult.Success; |                     return OperResult.Success; | ||||||
|                 using var timeoutToken = new CancellationTokenSource(TimeSpan.FromMilliseconds(_driverPropertys.ConnectTimeout)); |                 using var timeoutToken = new CancellationTokenSource(TimeSpan.FromMilliseconds(@this._driverPropertys.ConnectTimeout)); | ||||||
|                 using CancellationTokenSource stoppingToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, timeoutToken.Token); |                 using CancellationTokenSource stoppingToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, timeoutToken.Token); | ||||||
|                 if (_mqttClient?.IsConnected == true) |                 if (@this._mqttClient?.IsConnected == true) | ||||||
|                     return OperResult.Success; |                     return OperResult.Success; | ||||||
|                 if (_mqttClient == null) |                 if (@this._mqttClient == null) | ||||||
|                 { |                 { | ||||||
|                     return new OperResult("mqttClient is null"); |                     return new OperResult("mqttClient is null"); | ||||||
|                 } |                 } | ||||||
|                 var result = await _mqttClient.ConnectAsync(_mqttClientOptions, stoppingToken.Token).ConfigureAwait(false); |                 var result = await @this._mqttClient.ConnectAsync(@this._mqttClientOptions, stoppingToken.Token).ConfigureAwait(false); | ||||||
|                 if (_mqttClient.IsConnected) |                 if (@this._mqttClient.IsConnected) | ||||||
|                 { |                 { | ||||||
|                     return OperResult.Success; |                     return OperResult.Success; | ||||||
|                 } |                 } | ||||||
| @@ -570,7 +589,7 @@ public partial class MqttClient : BusinessBaseWithCacheIntervalScriptAll | |||||||
|             } |             } | ||||||
|             finally |             finally | ||||||
|             { |             { | ||||||
|                 ConnectLock.Release(); |                 @this.ConnectLock.Release(); | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -10,6 +10,9 @@ | |||||||
|  |  | ||||||
| using MQTTnet; | using MQTTnet; | ||||||
|  |  | ||||||
|  | using PooledAwait; | ||||||
|  |  | ||||||
|  |  | ||||||
| #if NET6_0 | #if NET6_0 | ||||||
| using MQTTnet.Client; | using MQTTnet.Client; | ||||||
| #endif | #endif | ||||||
| @@ -272,21 +275,29 @@ public partial class MqttCollect : CollectBase | |||||||
|  |  | ||||||
|         return list; |         return list; | ||||||
|     } |     } | ||||||
|     private async Task CheckAsync(object? state, CancellationToken cancellationToken) |     private Task CheckAsync(object? state, CancellationToken cancellationToken) | ||||||
|     { |     { | ||||||
|         var clientResult = await TryMqttClientAsync(cancellationToken).ConfigureAwait(false); |         return CheckAsync(this, cancellationToken); | ||||||
|         if (!clientResult.IsSuccess) |  | ||||||
|  |  | ||||||
|  |         static async PooledTask CheckAsync(MqttCollect @this, CancellationToken cancellationToken) | ||||||
|         { |         { | ||||||
|             if (cancellationToken.IsCancellationRequested) |             var clientResult = await @this.TryMqttClientAsync(cancellationToken).ConfigureAwait(false); | ||||||
|                 return; |             if (!clientResult.IsSuccess) | ||||||
|             if (success != clientResult.IsSuccess) |  | ||||||
|             { |             { | ||||||
|                 if (!clientResult.IsSuccess) |                 if (cancellationToken.IsCancellationRequested) | ||||||
|                     LogMessage?.LogWarning(clientResult.Exception, clientResult.ErrorMessage); |                     return; | ||||||
|                 success = clientResult.IsSuccess; |                 if (@this.success != clientResult.IsSuccess) | ||||||
|  |                 { | ||||||
|  |                     if (!clientResult.IsSuccess) | ||||||
|  |                         @this.LogMessage?.LogWarning(clientResult.Exception, clientResult.ErrorMessage); | ||||||
|  |                     @this.success = clientResult.IsSuccess; | ||||||
|  |                 } | ||||||
|  |                 await Task.Delay(10000, cancellationToken).ConfigureAwait(false); | ||||||
|  |                 //return; | ||||||
|             } |             } | ||||||
|             await Task.Delay(10000, cancellationToken).ConfigureAwait(false); |  | ||||||
|             //return; |             return; | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -10,6 +10,9 @@ | |||||||
|  |  | ||||||
| using MQTTnet; | using MQTTnet; | ||||||
|  |  | ||||||
|  | using PooledAwait; | ||||||
|  |  | ||||||
|  |  | ||||||
| #if NET6_0 | #if NET6_0 | ||||||
| using MQTTnet.Client; | using MQTTnet.Client; | ||||||
| #endif | #endif | ||||||
| @@ -129,32 +132,34 @@ public partial class MqttCollect : CollectBase | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private async ValueTask<OperResult> TryMqttClientAsync(CancellationToken cancellationToken) |     private ValueTask<OperResult> TryMqttClientAsync(CancellationToken cancellationToken) | ||||||
|     { |     { | ||||||
|         if (_mqttClient?.IsConnected == true) |         if (DisposedValue || _mqttClient == null) return TouchSocket.Core.EasyValueTask.FromResult(new OperResult("MqttClient is disposed")); | ||||||
|             return OperResult.Success; |  | ||||||
|         return await Client().ConfigureAwait(false); |  | ||||||
|  |  | ||||||
|         async ValueTask<OperResult> Client() |         if (_mqttClient?.IsConnected == true) | ||||||
|  |             return TouchSocket.Core.EasyValueTask.FromResult(OperResult.Success); | ||||||
|  |         return Client(this, cancellationToken); | ||||||
|  |  | ||||||
|  |         static async PooledValueTask<OperResult> Client(MqttCollect @this, CancellationToken cancellationToken) | ||||||
|         { |         { | ||||||
|             if (_mqttClient?.IsConnected == true) |             if (@this._mqttClient?.IsConnected == true) | ||||||
|                 return OperResult.Success; |                 return OperResult.Success; | ||||||
|             try |             try | ||||||
|             { |             { | ||||||
|                 await ConnectLock.WaitAsync(cancellationToken).ConfigureAwait(false); |                 await @this.ConnectLock.WaitAsync(cancellationToken).ConfigureAwait(false); | ||||||
|                 await Task.Delay(100, cancellationToken).ConfigureAwait(false); |                 await Task.Delay(100, cancellationToken).ConfigureAwait(false); | ||||||
|                 if (_mqttClient?.IsConnected == true) |                 if (@this._mqttClient?.IsConnected == true) | ||||||
|                     return OperResult.Success; |                     return OperResult.Success; | ||||||
|                 using var timeoutToken = new CancellationTokenSource(TimeSpan.FromMilliseconds(_driverPropertys.ConnectTimeout)); |                 using var timeoutToken = new CancellationTokenSource(TimeSpan.FromMilliseconds(@this._driverPropertys.ConnectTimeout)); | ||||||
|                 using CancellationTokenSource stoppingToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, timeoutToken.Token); |                 using CancellationTokenSource stoppingToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, timeoutToken.Token); | ||||||
|                 if (_mqttClient?.IsConnected == true) |                 if (@this._mqttClient?.IsConnected == true) | ||||||
|                     return OperResult.Success; |                     return OperResult.Success; | ||||||
|                 if (_mqttClient == null) |                 if (@this._mqttClient == null) | ||||||
|                 { |                 { | ||||||
|                     return new OperResult("mqttClient is null"); |                     return new OperResult("mqttClient is null"); | ||||||
|                 } |                 } | ||||||
|                 var result = await _mqttClient.ConnectAsync(_mqttClientOptions, stoppingToken.Token).ConfigureAwait(false); |                 var result = await @this._mqttClient.ConnectAsync(@this._mqttClientOptions, stoppingToken.Token).ConfigureAwait(false); | ||||||
|                 if (_mqttClient.IsConnected) |                 if (@this._mqttClient.IsConnected) | ||||||
|                 { |                 { | ||||||
|                     return OperResult.Success; |                     return OperResult.Success; | ||||||
|                 } |                 } | ||||||
| @@ -172,7 +177,7 @@ public partial class MqttCollect : CollectBase | |||||||
|             } |             } | ||||||
|             finally |             finally | ||||||
|             { |             { | ||||||
|                 ConnectLock.Release(); |                 @this.ConnectLock.Release(); | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -20,6 +20,8 @@ using MQTTnet.Server; | |||||||
|  |  | ||||||
| using Newtonsoft.Json.Linq; | using Newtonsoft.Json.Linq; | ||||||
|  |  | ||||||
|  | using PooledAwait; | ||||||
|  |  | ||||||
| using System.Text; | using System.Text; | ||||||
|  |  | ||||||
| using ThingsGateway.Admin.Application; | using ThingsGateway.Admin.Application; | ||||||
| @@ -165,25 +167,30 @@ public partial class MqttServer : BusinessBaseWithCacheIntervalScriptAll | |||||||
|     } |     } | ||||||
|     #region private |     #region private | ||||||
|  |  | ||||||
|     private async ValueTask<OperResult> Update(IEnumerable<TopicArray> topicArrayList, CancellationToken cancellationToken) |     private ValueTask<OperResult> Update(IEnumerable<TopicArray> topicArrayList, CancellationToken cancellationToken) | ||||||
|     { |     { | ||||||
|         foreach (var topicArray in topicArrayList) |         return Update(this, topicArrayList, cancellationToken); | ||||||
|  |  | ||||||
|  |         static async PooledValueTask<OperResult> Update(MqttServer @this, IEnumerable<TopicArray> topicArrayList, CancellationToken cancellationToken) | ||||||
|         { |         { | ||||||
|             var result = await MqttUpAsync(topicArray, cancellationToken).ConfigureAwait(false); |             foreach (var topicArray in topicArrayList) | ||||||
|             if (success != result.IsSuccess) |  | ||||||
|             { |             { | ||||||
|  |                 var result = await @this.MqttUpAsync(topicArray, cancellationToken).ConfigureAwait(false); | ||||||
|  |                 if (@this.success != result.IsSuccess) | ||||||
|  |                 { | ||||||
|  |                     if (!result.IsSuccess) | ||||||
|  |                     { | ||||||
|  |                         @this.LogMessage?.LogWarning(result.ToString()); | ||||||
|  |                     } | ||||||
|  |                     @this.success = result.IsSuccess; | ||||||
|  |                 } | ||||||
|                 if (!result.IsSuccess) |                 if (!result.IsSuccess) | ||||||
|                 { |                 { | ||||||
|                     LogMessage?.LogWarning(result.ToString()); |                     return result; | ||||||
|                 } |                 } | ||||||
|                 success = result.IsSuccess; |  | ||||||
|             } |  | ||||||
|             if (!result.IsSuccess) |  | ||||||
|             { |  | ||||||
|                 return result; |  | ||||||
|             } |             } | ||||||
|  |             return OperResult.Success; | ||||||
|         } |         } | ||||||
|         return OperResult.Success; |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private ValueTask<OperResult> UpdateAlarmModel(IEnumerable<AlarmVariable> item, CancellationToken cancellationToken) |     private ValueTask<OperResult> UpdateAlarmModel(IEnumerable<AlarmVariable> item, CancellationToken cancellationToken) | ||||||
| @@ -207,65 +214,70 @@ public partial class MqttServer : BusinessBaseWithCacheIntervalScriptAll | |||||||
|  |  | ||||||
|     #endregion private |     #endregion private | ||||||
|  |  | ||||||
|     private async ValueTask<Dictionary<string, Dictionary<string, IOperResult>>> GetRpcResult(string clientId, Dictionary<string, Dictionary<string, JToken>> rpcDatas) |     private ValueTask<Dictionary<string, Dictionary<string, IOperResult>>> GetRpcResult(string clientId, Dictionary<string, Dictionary<string, JToken>> rpcDatas) | ||||||
|     { |     { | ||||||
|         var mqttRpcResult = new Dictionary<string, Dictionary<string, IOperResult>>(); |         return GetRpcResult(this, clientId, rpcDatas); | ||||||
|         rpcDatas.ForEach(a => mqttRpcResult.Add(a.Key, new())); |  | ||||||
|         try |         static async PooledValueTask<Dictionary<string, Dictionary<string, IOperResult>>> GetRpcResult(MqttServer @this, string clientId, Dictionary<string, Dictionary<string, JToken>> rpcDatas) | ||||||
|         { |         { | ||||||
|             foreach (var rpcData in rpcDatas) |             var mqttRpcResult = new Dictionary<string, Dictionary<string, IOperResult>>(); | ||||||
|  |             rpcDatas.ForEach(a => mqttRpcResult.Add(a.Key, new())); | ||||||
|  |             try | ||||||
|             { |             { | ||||||
|                 if (GlobalData.ReadOnlyDevices.TryGetValue(rpcData.Key, out var device)) |                 foreach (var rpcData in rpcDatas) | ||||||
|                 { |                 { | ||||||
|                     foreach (var item in rpcData.Value) |                     if (GlobalData.ReadOnlyDevices.TryGetValue(rpcData.Key, out var device)) | ||||||
|                     { |                     { | ||||||
|                         if (device.ReadOnlyVariableRuntimes.TryGetValue(item.Key, out var variable) && IdVariableRuntimes.TryGetValue(variable.Id, out var tag)) |                         foreach (var item in rpcData.Value) | ||||||
|                         { |                         { | ||||||
|                             var rpcEnable = tag.GetPropertyValue(DeviceId, nameof(_variablePropertys.VariableRpcEnable))?.ToBoolean(); |                             if (device.ReadOnlyVariableRuntimes.TryGetValue(item.Key, out var variable) && @this.IdVariableRuntimes.TryGetValue(variable.Id, out var tag)) | ||||||
|                             if (rpcEnable == false) |  | ||||||
|                             { |                             { | ||||||
|                                 mqttRpcResult[rpcData.Key].Add(item.Key, new OperResult("RPCEnable is False")); |                                 var rpcEnable = tag.GetPropertyValue(@this.DeviceId, nameof(_variablePropertys.VariableRpcEnable))?.ToBoolean(); | ||||||
|  |                                 if (rpcEnable == false) | ||||||
|  |                                 { | ||||||
|  |                                     mqttRpcResult[rpcData.Key].Add(item.Key, new OperResult("RPCEnable is False")); | ||||||
|  |                                 } | ||||||
|  |                             } | ||||||
|  |                             else | ||||||
|  |                             { | ||||||
|  |                                 mqttRpcResult[rpcData.Key].Add(item.Key, new OperResult("The variable does not exist")); | ||||||
|                             } |                             } | ||||||
|                         } |                         } | ||||||
|                         else |                     } | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 Dictionary<string, Dictionary<string, string>> writeData = new(); | ||||||
|  |                 foreach (var item in rpcDatas) | ||||||
|  |                 { | ||||||
|  |                     writeData.Add(item.Key, new()); | ||||||
|  |  | ||||||
|  |                     foreach (var kv in item.Value) | ||||||
|  |                     { | ||||||
|  |                         if (!mqttRpcResult[item.Key].ContainsKey(kv.Key)) | ||||||
|                         { |                         { | ||||||
|                             mqttRpcResult[rpcData.Key].Add(item.Key, new OperResult("The variable does not exist")); |                             writeData[item.Key].Add(kv.Key, kv.Value?.ToString()); | ||||||
|                         } |                         } | ||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
|             } |  | ||||||
|  |  | ||||||
|             Dictionary<string, Dictionary<string, string>> writeData = new(); |                 var result = await GlobalData.RpcService.InvokeDeviceMethodAsync(@this.ToString() + "-" + clientId, | ||||||
|             foreach (var item in rpcDatas) |                     writeData).ConfigureAwait(false); | ||||||
|             { |  | ||||||
|                 writeData.Add(item.Key, new()); |  | ||||||
|  |  | ||||||
|                 foreach (var kv in item.Value) |                 foreach (var dictKv in result) | ||||||
|                 { |                 { | ||||||
|                     if (!mqttRpcResult[item.Key].ContainsKey(kv.Key)) |                     foreach (var item in dictKv.Value) | ||||||
|                     { |                     { | ||||||
|                         writeData[item.Key].Add(kv.Key, kv.Value?.ToString()); |                         mqttRpcResult[dictKv.Key].TryAdd(item.Key, item.Value); | ||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|  |             catch (Exception ex) | ||||||
|             var result = await GlobalData.RpcService.InvokeDeviceMethodAsync(ToString() + "-" + clientId, |  | ||||||
|                 writeData).ConfigureAwait(false); |  | ||||||
|  |  | ||||||
|             foreach (var dictKv in result) |  | ||||||
|             { |             { | ||||||
|                 foreach (var item in dictKv.Value) |                 @this.LogMessage?.LogWarning(ex); | ||||||
|                 { |  | ||||||
|                     mqttRpcResult[dictKv.Key].TryAdd(item.Key, item.Value); |  | ||||||
|                 } |  | ||||||
|             } |             } | ||||||
|         } |  | ||||||
|         catch (Exception ex) |  | ||||||
|         { |  | ||||||
|             LogMessage?.LogWarning(ex); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         return mqttRpcResult; |             return mqttRpcResult; | ||||||
|  |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private List<MqttApplicationMessage> GetRetainedMessages() |     private List<MqttApplicationMessage> GetRetainedMessages() | ||||||
| @@ -328,14 +340,19 @@ public partial class MqttServer : BusinessBaseWithCacheIntervalScriptAll | |||||||
|         return Task.CompletedTask; |         return Task.CompletedTask; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private async Task MqttServer_InterceptingPublishAsync(InterceptingPublishEventArgs args) |     private Task MqttServer_InterceptingPublishAsync(InterceptingPublishEventArgs args) | ||||||
|     { |     { | ||||||
|         try |         return MqttServer_InterceptingPublishAsync(this, args); | ||||||
|  |  | ||||||
|  |  | ||||||
|  |         static async PooledTask MqttServer_InterceptingPublishAsync(MqttServer @this, InterceptingPublishEventArgs args) | ||||||
|         { |         { | ||||||
|  |             try | ||||||
|  |             { | ||||||
| #if NET8_0_OR_GREATER | #if NET8_0_OR_GREATER | ||||||
|  |  | ||||||
|             var payload = args.ApplicationMessage.Payload; |                 var payload = args.ApplicationMessage.Payload; | ||||||
|             var payloadCount = payload.Length; |                 var payloadCount = payload.Length; | ||||||
| #else | #else | ||||||
|  |  | ||||||
|         var payload = args.ApplicationMessage.PayloadSegment; |         var payload = args.ApplicationMessage.PayloadSegment; | ||||||
| @@ -343,53 +360,56 @@ public partial class MqttServer : BusinessBaseWithCacheIntervalScriptAll | |||||||
|  |  | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
|             if (args.ApplicationMessage.Topic == _driverPropertys.RpcQuestTopic && payloadCount > 0) |                 if (args.ApplicationMessage.Topic == @this._driverPropertys.RpcQuestTopic && payloadCount > 0) | ||||||
|             { |  | ||||||
|                 var data = GetRetainedMessages(); |  | ||||||
|  |  | ||||||
|                 foreach (var item in data) |  | ||||||
|                 { |                 { | ||||||
|                     await _mqttServer.InjectApplicationMessage(new InjectedMqttApplicationMessage(item)).ConfigureAwait(false); |                     var data = @this.GetRetainedMessages(); | ||||||
|                 } |  | ||||||
|                 return; |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             if (!_driverPropertys.DeviceRpcEnable || string.IsNullOrEmpty(args.ClientId)) |                     foreach (var item in data) | ||||||
|                 return; |                     { | ||||||
|  |                         await @this._mqttServer.InjectApplicationMessage(new InjectedMqttApplicationMessage(item)).ConfigureAwait(false); | ||||||
|             if (!_driverPropertys.BigTextScriptRpc.IsNullOrEmpty()) |                     } | ||||||
|             { |  | ||||||
|                 var rpcBase = CSharpScriptEngineExtension.Do<DynamicMqttServerRpcBase>(_driverPropertys.BigTextScriptRpc); |  | ||||||
|  |  | ||||||
|                 await rpcBase.RPCInvokeAsync(LogMessage, args, _driverPropertys, _mqttServer, GetRpcResult, CancellationToken.None).ConfigureAwait(false); |  | ||||||
|             } |  | ||||||
|             else |  | ||||||
|             { |  | ||||||
|                 if (_driverPropertys.RpcWriteTopic.IsNullOrWhiteSpace()) return; |  | ||||||
|  |  | ||||||
|                 var t = string.Format(null, RpcTopic, _driverPropertys.RpcWriteTopic); |  | ||||||
|                 if (MqttTopicFilterComparer.Compare(args.ApplicationMessage.Topic, t) != MqttTopicFilterCompareResult.IsMatch) |  | ||||||
|                     return; |                     return; | ||||||
|                 var rpcDatas = Encoding.UTF8.GetString(payload).FromJsonNetString<Dictionary<string, Dictionary<string, JToken>>>(); |  | ||||||
|                 if (rpcDatas == null) |  | ||||||
|                     return; |  | ||||||
|                 var mqttRpcResult = await GetRpcResult(args.ClientId, rpcDatas).ConfigureAwait(false); |  | ||||||
|  |  | ||||||
|                 try |  | ||||||
|                 { |  | ||||||
|                     var variableMessage = new MqttApplicationMessageBuilder() |  | ||||||
|         .WithTopic($"{args.ApplicationMessage.Topic}/Response") |  | ||||||
|         .WithPayload(mqttRpcResult.ToSystemTextJsonString(_driverPropertys.JsonFormattingIndented)).Build(); |  | ||||||
|                     await _mqttServer.InjectApplicationMessage(new InjectedMqttApplicationMessage(variableMessage)).ConfigureAwait(false); |  | ||||||
|                 } |                 } | ||||||
|                 catch |  | ||||||
|  |                 if (!@this._driverPropertys.DeviceRpcEnable || string.IsNullOrEmpty(args.ClientId)) | ||||||
|  |                     return; | ||||||
|  |  | ||||||
|  |                 if (!@this._driverPropertys.BigTextScriptRpc.IsNullOrEmpty()) | ||||||
|                 { |                 { | ||||||
|  |                     var rpcBase = CSharpScriptEngineExtension.Do<DynamicMqttServerRpcBase>(@this._driverPropertys.BigTextScriptRpc); | ||||||
|  |  | ||||||
|  |                     await rpcBase.RPCInvokeAsync(@this.LogMessage, args, @this._driverPropertys, @this._mqttServer, @this.GetRpcResult, CancellationToken.None).ConfigureAwait(false); | ||||||
|  |                 } | ||||||
|  |                 else | ||||||
|  |                 { | ||||||
|  |                     if (@this._driverPropertys.RpcWriteTopic.IsNullOrWhiteSpace()) return; | ||||||
|  |  | ||||||
|  |                     var t = string.Format(null, RpcTopic, @this._driverPropertys.RpcWriteTopic); | ||||||
|  |                     if (MqttTopicFilterComparer.Compare(args.ApplicationMessage.Topic, t) != MqttTopicFilterCompareResult.IsMatch) | ||||||
|  |                         return; | ||||||
|  |                     var rpcDatas = Encoding.UTF8.GetString(payload).FromJsonNetString<Dictionary<string, Dictionary<string, JToken>>>(); | ||||||
|  |                     if (rpcDatas == null) | ||||||
|  |                         return; | ||||||
|  |                     var mqttRpcResult = await @this.GetRpcResult(args.ClientId, rpcDatas).ConfigureAwait(false); | ||||||
|  |  | ||||||
|  |                     try | ||||||
|  |                     { | ||||||
|  |                         var variableMessage = new MqttApplicationMessageBuilder() | ||||||
|  |             .WithTopic($"{args.ApplicationMessage.Topic}/Response") | ||||||
|  |             .WithPayload(mqttRpcResult.ToSystemTextJsonString(@this._driverPropertys.JsonFormattingIndented)).Build(); | ||||||
|  |                         await @this._mqttServer.InjectApplicationMessage(new InjectedMqttApplicationMessage(variableMessage)).ConfigureAwait(false); | ||||||
|  |                     } | ||||||
|  |                     catch | ||||||
|  |                     { | ||||||
|  |                     } | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|         } |             catch (Exception ex) | ||||||
|         catch (Exception ex) |             { | ||||||
|         { |                 @this.LogMessage?.LogWarning(ex, $"MqttServer_InterceptingPublishAsync error"); | ||||||
|             LogMessage?.LogWarning(ex, $"MqttServer_InterceptingPublishAsync error"); |             } | ||||||
|  |  | ||||||
|  |             return; | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -432,33 +452,38 @@ public partial class MqttServer : BusinessBaseWithCacheIntervalScriptAll | |||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// 上传mqtt,返回上传结果 |     /// 上传mqtt,返回上传结果 | ||||||
|     /// </summary> |     /// </summary> | ||||||
|     public async ValueTask<OperResult> MqttUpAsync(TopicArray topicArray, CancellationToken cancellationToken = default) |     public ValueTask<OperResult> MqttUpAsync(TopicArray topicArray, CancellationToken cancellationToken = default) | ||||||
|     { |     { | ||||||
|         try |         return MqttUpAsync(this, topicArray, cancellationToken); | ||||||
|         { |  | ||||||
|             var message = new MqttApplicationMessageBuilder() |  | ||||||
| .WithTopic(topicArray.Topic).WithQualityOfServiceLevel(_driverPropertys.MqttQualityOfServiceLevel).WithRetainFlag() |  | ||||||
| .WithPayload(topicArray.Payload).Build(); |  | ||||||
|             await _mqttServer.InjectApplicationMessage( |  | ||||||
|                     new InjectedMqttApplicationMessage(message), cancellationToken).ConfigureAwait(false); |  | ||||||
|  |  | ||||||
|             if (_driverPropertys.DetailLog) |         static async PooledValueTask<OperResult> MqttUpAsync(MqttServer @this, TopicArray topicArray, CancellationToken cancellationToken) | ||||||
|             { |  | ||||||
|                 if (LogMessage?.LogLevel <= TouchSocket.Core.LogLevel.Trace) |  | ||||||
|                     LogMessage?.LogTrace(GetDetailLogString(topicArray, _memoryVarModelQueue.Count)); |  | ||||||
|                 else if (LogMessage?.LogLevel <= TouchSocket.Core.LogLevel.Debug) |  | ||||||
|                     LogMessage?.LogDebug(GetCountLogString(topicArray, _memoryVarModelQueue.Count)); |  | ||||||
|             } |  | ||||||
|             else |  | ||||||
|             { |  | ||||||
|                 if (LogMessage?.LogLevel <= TouchSocket.Core.LogLevel.Debug) |  | ||||||
|                     LogMessage?.LogDebug(GetCountLogString(topicArray, _memoryVarModelQueue.Count)); |  | ||||||
|             } |  | ||||||
|             return OperResult.Success; |  | ||||||
|         } |  | ||||||
|         catch (Exception ex) |  | ||||||
|         { |         { | ||||||
|             return new OperResult("Upload fail", ex); |             try | ||||||
|  |             { | ||||||
|  |                 var message = new MqttApplicationMessageBuilder() | ||||||
|  |     .WithTopic(topicArray.Topic).WithQualityOfServiceLevel(@this._driverPropertys.MqttQualityOfServiceLevel).WithRetainFlag() | ||||||
|  |     .WithPayload(topicArray.Payload).Build(); | ||||||
|  |                 await @this._mqttServer.InjectApplicationMessage( | ||||||
|  |                         new InjectedMqttApplicationMessage(message), cancellationToken).ConfigureAwait(false); | ||||||
|  |  | ||||||
|  |                 if (@this._driverPropertys.DetailLog) | ||||||
|  |                 { | ||||||
|  |                     if (@this.LogMessage?.LogLevel <= TouchSocket.Core.LogLevel.Trace) | ||||||
|  |                         @this.LogMessage?.LogTrace(@this.GetDetailLogString(topicArray, @this._memoryVarModelQueue.Count)); | ||||||
|  |                     else if (@this.LogMessage?.LogLevel <= TouchSocket.Core.LogLevel.Debug) | ||||||
|  |                         @this.LogMessage?.LogDebug(@this.GetCountLogString(topicArray, @this._memoryVarModelQueue.Count)); | ||||||
|  |                 } | ||||||
|  |                 else | ||||||
|  |                 { | ||||||
|  |                     if (@this.LogMessage?.LogLevel <= TouchSocket.Core.LogLevel.Debug) | ||||||
|  |                         @this.LogMessage?.LogDebug(@this.GetCountLogString(topicArray, @this._memoryVarModelQueue.Count)); | ||||||
|  |                 } | ||||||
|  |                 return OperResult.Success; | ||||||
|  |             } | ||||||
|  |             catch (Exception ex) | ||||||
|  |             { | ||||||
|  |                 return new OperResult("Upload fail", ex); | ||||||
|  |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -160,7 +160,6 @@ public class OpcDaMaster : CollectBase | |||||||
|     protected override async ValueTask<Dictionary<string, OperResult>> WriteValuesAsync(Dictionary<VariableRuntime, JToken> writeInfoLists, CancellationToken cancellationToken) |     protected override async ValueTask<Dictionary<string, OperResult>> WriteValuesAsync(Dictionary<VariableRuntime, JToken> writeInfoLists, CancellationToken cancellationToken) | ||||||
|     { |     { | ||||||
|         using var writeLock = await ReadWriteLock.WriterLockAsync(cancellationToken).ConfigureAwait(false); |         using var writeLock = await ReadWriteLock.WriterLockAsync(cancellationToken).ConfigureAwait(false); | ||||||
|         await ValueTask.CompletedTask.ConfigureAwait(false); |  | ||||||
|         var result = _plc.WriteItem(writeInfoLists.ToDictionary(a => a.Key.RegisterAddress!, a => a.Value.GetObjectFromJToken()!)); |         var result = _plc.WriteItem(writeInfoLists.ToDictionary(a => a.Key.RegisterAddress!, a => a.Value.GetObjectFromJToken()!)); | ||||||
|         var results = new NonBlockingDictionary<string, OperResult>(result.ToDictionary<KeyValuePair<string, Tuple<bool, string>>, string, OperResult>(a => writeInfoLists.Keys.FirstOrDefault(b => b.RegisterAddress == a.Key).Name, a => |         var results = new NonBlockingDictionary<string, OperResult>(result.ToDictionary<KeyValuePair<string, Tuple<bool, string>>, string, OperResult>(a => writeInfoLists.Keys.FirstOrDefault(b => b.RegisterAddress == a.Key).Name, a => | ||||||
|         { |         { | ||||||
|   | |||||||
| @@ -13,6 +13,8 @@ using Newtonsoft.Json.Linq; | |||||||
| using Opc.Ua; | using Opc.Ua; | ||||||
| using Opc.Ua.Client; | using Opc.Ua.Client; | ||||||
|  |  | ||||||
|  | using PooledAwait; | ||||||
|  |  | ||||||
| using System.Collections.Concurrent; | using System.Collections.Concurrent; | ||||||
|  |  | ||||||
| using ThingsGateway.Foundation.Extension.Generic; | using ThingsGateway.Foundation.Extension.Generic; | ||||||
| @@ -150,50 +152,58 @@ public class OpcUaMaster : CollectBase | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     private volatile bool checkLog; |     private volatile bool checkLog; | ||||||
|     private async Task CheckAsync(object? state, CancellationToken cancellationToken) |     private Task CheckAsync(object? state, CancellationToken cancellationToken) | ||||||
|     { |     { | ||||||
|         if (_plc.Session != null) |         return CheckAsync(this, cancellationToken); | ||||||
|  |  | ||||||
|  |  | ||||||
|  |         static async PooledTask CheckAsync(OpcUaMaster @this, CancellationToken cancellationToken) | ||||||
|         { |         { | ||||||
|             if (_driverProperties.ActiveSubscribe) |             if (@this._plc.Session != null) | ||||||
|             { |             { | ||||||
|                 //获取设备连接状态 |                 if (@this._driverProperties.ActiveSubscribe) | ||||||
|                 if (IsConnected()) |  | ||||||
|                 { |                 { | ||||||
|                     //更新设备活动时间 |                     //获取设备连接状态 | ||||||
|  |                     if (@this.IsConnected()) | ||||||
|                     { |                     { | ||||||
|                         //如果是订阅模式,连接时添加订阅组 |                         //更新设备活动时间 | ||||||
|                         if (_plc.OpcUaProperty?.ActiveSubscribe == true && CurrentDevice.VariableSourceReads.Count > 0 && _plc.Session.SubscriptionCount < CurrentDevice.VariableSourceReads.Count) |  | ||||||
|                         { |                         { | ||||||
|                             if (cancellationToken.IsCancellationRequested) return; |                             //如果是订阅模式,连接时添加订阅组 | ||||||
|                             foreach (var variableSourceRead in CurrentDevice.VariableSourceReads) |                             if (@this._plc.OpcUaProperty?.ActiveSubscribe == true && @this.CurrentDevice.VariableSourceReads.Count > 0 && @this._plc.Session.SubscriptionCount < @this.CurrentDevice.VariableSourceReads.Count) | ||||||
|                             { |                             { | ||||||
|                                 if (cancellationToken.IsCancellationRequested) return; |                                 if (cancellationToken.IsCancellationRequested) return; | ||||||
|                                 try |                                 foreach (var variableSourceRead in @this.CurrentDevice.VariableSourceReads) | ||||||
|                                 { |                                 { | ||||||
|                                     if (_plc.Session.Subscriptions.FirstOrDefault(a => a.DisplayName == variableSourceRead.RegisterAddress) == null) |                                     if (cancellationToken.IsCancellationRequested) return; | ||||||
|  |                                     try | ||||||
|                                     { |                                     { | ||||||
|                                         await _plc.AddSubscriptionAsync(variableSourceRead.RegisterAddress, variableSourceRead.VariableRuntimes.Where(a => !a.RegisterAddress.IsNullOrEmpty()).Select(a => a.RegisterAddress!).ToHashSet().ToArray(), _plc.OpcUaProperty.LoadType, cancellationToken).ConfigureAwait(false); |                                         if (@this._plc.Session.Subscriptions.FirstOrDefault(a => a.DisplayName == variableSourceRead.RegisterAddress) == null) | ||||||
|  |                                         { | ||||||
|  |                                             await @this._plc.AddSubscriptionAsync(variableSourceRead.RegisterAddress, variableSourceRead.VariableRuntimes.Where(a => !a.RegisterAddress.IsNullOrEmpty()).Select(a => a.RegisterAddress!).ToHashSet().ToArray(), @this._plc.OpcUaProperty.LoadType, cancellationToken).ConfigureAwait(false); | ||||||
|  |  | ||||||
|                                         LogMessage?.LogInformation($"AddSubscription index  {CurrentDevice.VariableSourceReads.IndexOf(variableSourceRead)}  done"); |                                             @this.LogMessage?.LogInformation($"AddSubscription index  {@this.CurrentDevice.VariableSourceReads.IndexOf(variableSourceRead)}  done"); | ||||||
|  |                                         } | ||||||
|  |  | ||||||
|  |                                         await Task.Delay(100, cancellationToken).ConfigureAwait(false); // allow for subscription to be finished on server? | ||||||
|  |  | ||||||
|  |                                         @this.checkLog = true; | ||||||
|  |                                     } | ||||||
|  |                                     catch (Exception ex) | ||||||
|  |                                     { | ||||||
|  |                                         if (!@this.checkLog) | ||||||
|  |                                             @this.LogMessage?.LogWarning(ex, "AddSubscriptions error"); | ||||||
|  |                                         @this.checkLog = false; | ||||||
|                                     } |                                     } | ||||||
|  |  | ||||||
|                                     await Task.Delay(100, cancellationToken).ConfigureAwait(false); // allow for subscription to be finished on server? |  | ||||||
|  |  | ||||||
|                                     checkLog = true; |  | ||||||
|                                 } |                                 } | ||||||
|                                 catch (Exception ex) |                                 @this.LogMessage?.LogInformation("AddSubscriptions done"); | ||||||
|                                 { |  | ||||||
|                                     if (!checkLog) |  | ||||||
|                                         LogMessage?.LogWarning(ex, "AddSubscriptions error"); |  | ||||||
|                                     checkLog = false; |  | ||||||
|                                 } |  | ||||||
|  |  | ||||||
|                             } |                             } | ||||||
|                             LogMessage?.LogInformation("AddSubscriptions done"); |  | ||||||
|                         } |                         } | ||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|  |  | ||||||
|  |             return; | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -230,51 +240,56 @@ public class OpcUaMaster : CollectBase | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     /// <inheritdoc/> |     /// <inheritdoc/> | ||||||
|     protected override async ValueTask<OperResult<ReadOnlyMemory<byte>>> ReadSourceAsync(VariableSourceRead deviceVariableSourceRead, CancellationToken cancellationToken) |     protected override ValueTask<OperResult<ReadOnlyMemory<byte>>> ReadSourceAsync(VariableSourceRead deviceVariableSourceRead, CancellationToken cancellationToken) | ||||||
|     { |     { | ||||||
|         DateTime time = DateTime.Now; |         return ReadSourceAsync(this, deviceVariableSourceRead, cancellationToken); | ||||||
|         var addresss = deviceVariableSourceRead.VariableRuntimes.Where(a => !a.RegisterAddress.IsNullOrEmpty()).Select(a => a.RegisterAddress!).ToArray(); |  | ||||||
|         try |         static async PooledValueTask<OperResult<ReadOnlyMemory<byte>>> ReadSourceAsync(OpcUaMaster @this, VariableSourceRead deviceVariableSourceRead, CancellationToken cancellationToken) | ||||||
|         { |         { | ||||||
|             var result = await _plc.ReadJTokenValueAsync(addresss, cancellationToken).ConfigureAwait(false); |             DateTime time = DateTime.Now; | ||||||
|             foreach (var data in result) |             var addresss = deviceVariableSourceRead.VariableRuntimes.Where(a => !a.RegisterAddress.IsNullOrEmpty()).Select(a => a.RegisterAddress!).ToArray(); | ||||||
|  |             try | ||||||
|             { |             { | ||||||
|                 if (!cancellationToken.IsCancellationRequested) |                 var result = await @this._plc.ReadJTokenValueAsync(addresss, cancellationToken).ConfigureAwait(false); | ||||||
|  |                 foreach (var data in result) | ||||||
|                 { |                 { | ||||||
|                     var data1 = deviceVariableSourceRead.VariableRuntimes.Where(a => a.RegisterAddress == data.Item1); |                     if (!cancellationToken.IsCancellationRequested) | ||||||
|  |  | ||||||
|                     foreach (var item in data1) |  | ||||||
|                     { |                     { | ||||||
|                         object value = data.Item3.GetObjectFromJToken(); |                         var data1 = deviceVariableSourceRead.VariableRuntimes.Where(a => a.RegisterAddress == data.Item1); | ||||||
|  |  | ||||||
|                         var isGood = StatusCode.IsGood(data.Item2.StatusCode); |                         foreach (var item in data1) | ||||||
|                         if (_driverProperties.SourceTimestampEnable) |  | ||||||
|                         { |                         { | ||||||
|                             time = data.Item2.SourceTimestamp.ToLocalTime(); |                             object value = data.Item3.GetObjectFromJToken(); | ||||||
|                         } |  | ||||||
|                         if (isGood) |                             var isGood = StatusCode.IsGood(data.Item2.StatusCode); | ||||||
|                         { |                             if (@this._driverProperties.SourceTimestampEnable) | ||||||
|                             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}"); |                                 time = data.Item2.SourceTimestamp.ToLocalTime(); | ||||||
|  |                             } | ||||||
|  |                             if (isGood) | ||||||
|  |                             { | ||||||
|  |                                 item.SetValue(value, time); | ||||||
|  |                             } | ||||||
|  |                             else | ||||||
|  |                             { | ||||||
|  |                                 if (item is VariableRuntime variable && (variable.IsOnline || variable.CollectTime == DateTime.UnixEpoch.ToLocalTime())) | ||||||
|  |                                 { | ||||||
|  |                                     @this.LogMessage?.LogWarning($"OPC quality bad:{Environment.NewLine}{data.Item1}"); | ||||||
|  |                                 } | ||||||
|  |                                 item.SetValue(null, time, false); | ||||||
|  |                                 item.VariableSource.LastErrorMessage = data.Item2.StatusCode.ToString(); | ||||||
|                             } |                             } | ||||||
|                             item.SetValue(null, time, false); |  | ||||||
|                             item.VariableSource.LastErrorMessage = data.Item2.StatusCode.ToString(); |  | ||||||
|                         } |                         } | ||||||
|  |                         @this.LogMessage?.Trace($"Change:{Environment.NewLine}{data.Item1} : {data.Item3}"); | ||||||
|                     } |                     } | ||||||
|                     LogMessage?.Trace($"Change:{Environment.NewLine}{data.Item1} : {data.Item3}"); |  | ||||||
|                 } |                 } | ||||||
|             } |  | ||||||
|  |  | ||||||
|             return OperResult.CreateSuccessResult<ReadOnlyMemory<byte>>(null); |                 return OperResult.CreateSuccessResult<ReadOnlyMemory<byte>>(null); | ||||||
|         } |             } | ||||||
|         catch (Exception ex) |             catch (Exception ex) | ||||||
|         { |             { | ||||||
|             return new OperResult<ReadOnlyMemory<byte>>($"ReadSourceAsync {addresss.ToSystemTextJsonString()}:{Environment.NewLine}{ex}"); |                 return new OperResult<ReadOnlyMemory<byte>>($"ReadSourceAsync {addresss.ToSystemTextJsonString()}:{Environment.NewLine}{ex}"); | ||||||
|  |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -15,6 +15,8 @@ using Opc.Ua; | |||||||
| using Opc.Ua.Bindings; | using Opc.Ua.Bindings; | ||||||
| using Opc.Ua.Configuration; | using Opc.Ua.Configuration; | ||||||
|  |  | ||||||
|  | using PooledAwait; | ||||||
|  |  | ||||||
| using System.Collections.Concurrent; | using System.Collections.Concurrent; | ||||||
| using System.Reflection; | using System.Reflection; | ||||||
|  |  | ||||||
| @@ -197,56 +199,61 @@ public partial class OpcUaServer : BusinessBase | |||||||
|         await base.ProtectedStartAsync(cancellationToken).ConfigureAwait(false); |         await base.ProtectedStartAsync(cancellationToken).ConfigureAwait(false); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     protected override async Task ProtectedExecuteAsync(object? state, CancellationToken cancellationToken) |     protected override Task ProtectedExecuteAsync(object? state, CancellationToken cancellationToken) | ||||||
|     { |     { | ||||||
|         try |         return ProtectedExecuteAsync(this, cancellationToken); | ||||||
|  |  | ||||||
|  |         static async PooledTask ProtectedExecuteAsync(OpcUaServer @this, CancellationToken cancellationToken) | ||||||
|         { |         { | ||||||
|             if (!IsConnected()) |             try | ||||||
|             { |             { | ||||||
|                 try |                 if (!@this.IsConnected()) | ||||||
|                 { |                 { | ||||||
|                     await Task.Delay(2000, cancellationToken).ConfigureAwait(false); |                     try | ||||||
|                     await m_application.CheckApplicationInstanceCertificatesAsync(true, 1200, cancellationToken).ConfigureAwait(false); |  | ||||||
|                     await m_application.StartAsync(m_server).ConfigureAwait(false); |  | ||||||
|                     connect_success = true; |  | ||||||
|                     await Task.Delay(2000, cancellationToken).ConfigureAwait(false); |  | ||||||
|                     IdVariableRuntimes.ForEach(a => VariableValueChange(a.Value, a.Value.AdaptVariableBasicData())); |  | ||||||
|                 } |  | ||||||
|                 catch (Exception ex) |  | ||||||
|                 { |  | ||||||
|                     if (connect_success) |  | ||||||
|                         LogMessage?.LogWarning(ex, "Failed to start service"); |  | ||||||
|                     connect_success = false; |  | ||||||
|                     await Task.Delay(10000, cancellationToken).ConfigureAwait(false); |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|             var varList = CollectVariableRuntimes.ToListWithDequeue(); |  | ||||||
|             foreach (var item in varList) |  | ||||||
|             { |  | ||||||
|                 try |  | ||||||
|                 { |  | ||||||
|                     if (!cancellationToken.IsCancellationRequested) |  | ||||||
|                     { |                     { | ||||||
|                         m_server?.NodeManager?.UpVariable(item); |                         await Task.Delay(2000, cancellationToken).ConfigureAwait(false); | ||||||
|  |                         await @this.m_application.CheckApplicationInstanceCertificatesAsync(true, 1200, cancellationToken).ConfigureAwait(false); | ||||||
|  |                         await @this.m_application.StartAsync(@this.m_server).ConfigureAwait(false); | ||||||
|  |                         @this.connect_success = true; | ||||||
|  |                         await Task.Delay(2000, cancellationToken).ConfigureAwait(false); | ||||||
|  |                         @this.IdVariableRuntimes.ForEach(a => @this.VariableValueChange(a.Value, a.Value.AdaptVariableBasicData())); | ||||||
|                     } |                     } | ||||||
|                     else |                     catch (Exception ex) | ||||||
|                     { |                     { | ||||||
|                         break; |                         if (@this.connect_success) | ||||||
|  |                             @this.LogMessage?.LogWarning(ex, "Failed to start service"); | ||||||
|  |                         @this.connect_success = false; | ||||||
|  |                         await Task.Delay(10000, cancellationToken).ConfigureAwait(false); | ||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
|                 catch (Exception ex) |                 var varList = @this.CollectVariableRuntimes.ToListWithDequeue(); | ||||||
|  |                 foreach (var item in varList) | ||||||
|                 { |                 { | ||||||
|                     LogMessage?.LogWarning(ex); |                     try | ||||||
|  |                     { | ||||||
|  |                         if (!cancellationToken.IsCancellationRequested) | ||||||
|  |                         { | ||||||
|  |                             @this.m_server?.NodeManager?.UpVariable(item); | ||||||
|  |                         } | ||||||
|  |                         else | ||||||
|  |                         { | ||||||
|  |                             break; | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                     catch (Exception ex) | ||||||
|  |                     { | ||||||
|  |                         @this.LogMessage?.LogWarning(ex); | ||||||
|  |                     } | ||||||
|                 } |                 } | ||||||
|  |                 @this.success = true; | ||||||
|  |             } | ||||||
|  |             catch (OperationCanceledException) { } | ||||||
|  |             catch (Exception ex) | ||||||
|  |             { | ||||||
|  |                 if (@this.success) | ||||||
|  |                     @this.LogMessage?.LogWarning(ex); | ||||||
|  |                 @this.success = false; | ||||||
|             } |             } | ||||||
|             success = true; |  | ||||||
|         } |  | ||||||
|         catch (OperationCanceledException) { } |  | ||||||
|         catch (Exception ex) |  | ||||||
|         { |  | ||||||
|             if (success) |  | ||||||
|                 LogMessage?.LogWarning(ex); |  | ||||||
|             success = false; |  | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -8,6 +8,8 @@ | |||||||
| //  QQ群:605534569 | //  QQ群:605534569 | ||||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||||
|  |  | ||||||
|  | using PooledAwait; | ||||||
|  |  | ||||||
| using RabbitMQ.Client; | using RabbitMQ.Client; | ||||||
|  |  | ||||||
| using ThingsGateway.Foundation; | using ThingsGateway.Foundation; | ||||||
| @@ -64,39 +66,44 @@ public partial class RabbitMQProducer : BusinessBaseWithCacheIntervalScriptAll | |||||||
|         await base.DisposeAsync(disposing).ConfigureAwait(false); |         await base.DisposeAsync(disposing).ConfigureAwait(false); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     protected override async Task ProtectedExecuteAsync(object? state, CancellationToken cancellationToken) |     protected override Task ProtectedExecuteAsync(object? state, CancellationToken cancellationToken) | ||||||
|     { |     { | ||||||
|         if (_channel == null) |         return ProtectedExecuteAsync(this, cancellationToken); | ||||||
|  |  | ||||||
|  |         static async PooledTask ProtectedExecuteAsync(RabbitMQProducer @this, CancellationToken cancellationToken) | ||||||
|         { |         { | ||||||
|             try |             if (@this._channel == null) | ||||||
|             { |             { | ||||||
|                 // 创建连接 |                 try | ||||||
|                 _connection ??= await _connectionFactory.CreateConnectionAsync(cancellationToken).ConfigureAwait(false); |  | ||||||
|                 // 创建通道 |  | ||||||
|                 _channel ??= await _connection.CreateChannelAsync(cancellationToken: cancellationToken).ConfigureAwait(false); |  | ||||||
|                 // 声明路由队列 |  | ||||||
|                 if (_driverPropertys.IsQueueDeclare) |  | ||||||
|                 { |                 { | ||||||
|                     await (_channel?.QueueDeclareAsync(_driverPropertys.VariableTopic, true, false, false, cancellationToken: cancellationToken)).ConfigureAwait(false); |                     // 创建连接 | ||||||
|                     await (_channel?.QueueDeclareAsync(_driverPropertys.DeviceTopic, true, false, false, cancellationToken: cancellationToken)).ConfigureAwait(false); |                     @this._connection ??= await @this._connectionFactory.CreateConnectionAsync(cancellationToken).ConfigureAwait(false); | ||||||
|                     await (_channel?.QueueDeclareAsync(_driverPropertys.AlarmTopic, true, false, false, cancellationToken: cancellationToken)).ConfigureAwait(false); |                     // 创建通道 | ||||||
|  |                     @this._channel ??= await @this._connection.CreateChannelAsync(cancellationToken: cancellationToken).ConfigureAwait(false); | ||||||
|  |                     // 声明路由队列 | ||||||
|  |                     if (@this._driverPropertys.IsQueueDeclare) | ||||||
|  |                     { | ||||||
|  |                         await (@this._channel?.QueueDeclareAsync(@this._driverPropertys.VariableTopic, true, false, false, cancellationToken: cancellationToken)).ConfigureAwait(false); | ||||||
|  |                         await (@this._channel?.QueueDeclareAsync(@this._driverPropertys.DeviceTopic, true, false, false, cancellationToken: cancellationToken)).ConfigureAwait(false); | ||||||
|  |                         await (@this._channel?.QueueDeclareAsync(@this._driverPropertys.AlarmTopic, true, false, false, cancellationToken: cancellationToken)).ConfigureAwait(false); | ||||||
|  |                     } | ||||||
|  |                     @this.success = true; | ||||||
|  |                 } | ||||||
|  |                 catch (Exception ex) | ||||||
|  |                 { | ||||||
|  |                     if (@this.success) | ||||||
|  |                     { | ||||||
|  |                         @this.LogMessage?.LogWarning(ex); | ||||||
|  |                         @this.success = false; | ||||||
|  |                     } | ||||||
|  |                     await Task.Delay(10000, cancellationToken).ConfigureAwait(false); | ||||||
|                 } |                 } | ||||||
|                 success = true; |  | ||||||
|             } |             } | ||||||
|             catch (Exception ex) |             else | ||||||
|             { |             { | ||||||
|                 if (success) |                 await @this.Update(cancellationToken).ConfigureAwait(false); | ||||||
|                 { |  | ||||||
|                     LogMessage?.LogWarning(ex); |  | ||||||
|                     success = false; |  | ||||||
|                 } |  | ||||||
|                 await Task.Delay(10000, cancellationToken).ConfigureAwait(false); |  | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|         else |  | ||||||
|         { |  | ||||||
|             await Update(cancellationToken).ConfigureAwait(false); |  | ||||||
|         } |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
| #endif | #endif | ||||||
|   | |||||||
| @@ -8,6 +8,8 @@ | |||||||
| //  QQ群:605534569 | //  QQ群:605534569 | ||||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||||
|  |  | ||||||
|  | using PooledAwait; | ||||||
|  |  | ||||||
| using RabbitMQ.Client; | using RabbitMQ.Client; | ||||||
|  |  | ||||||
| using ThingsGateway.Extension.Generic; | using ThingsGateway.Extension.Generic; | ||||||
| @@ -148,26 +150,30 @@ public partial class RabbitMQProducer : BusinessBaseWithCacheIntervalScriptAll | |||||||
|  |  | ||||||
|     #region private |     #region private | ||||||
|  |  | ||||||
|     private async ValueTask<OperResult> Update(IEnumerable<TopicArray> topicArrayList, CancellationToken cancellationToken) |     private ValueTask<OperResult> Update(IEnumerable<TopicArray> topicArrayList, CancellationToken cancellationToken) | ||||||
|     { |     { | ||||||
|         foreach (var topicArray in topicArrayList) |         return Update(this, topicArrayList, cancellationToken); | ||||||
|  |  | ||||||
|  |         static async PooledValueTask<OperResult> Update(RabbitMQProducer @this, IEnumerable<TopicArray> topicArrayList, CancellationToken cancellationToken) | ||||||
|         { |         { | ||||||
|             var result = await RabbitMQUpAsync(topicArray, cancellationToken).ConfigureAwait(false); |             foreach (var topicArray in topicArrayList) | ||||||
|             if (success != result.IsSuccess) |  | ||||||
|             { |             { | ||||||
|  |                 var result = await @this.RabbitMQUpAsync(topicArray, cancellationToken).ConfigureAwait(false); | ||||||
|  |                 if (@this.success != result.IsSuccess) | ||||||
|  |                 { | ||||||
|  |                     if (!result.IsSuccess) | ||||||
|  |                     { | ||||||
|  |                         @this.LogMessage?.LogWarning(result.ToString()); | ||||||
|  |                     } | ||||||
|  |                     @this.success = result.IsSuccess; | ||||||
|  |                 } | ||||||
|                 if (!result.IsSuccess) |                 if (!result.IsSuccess) | ||||||
|                 { |                 { | ||||||
|                     LogMessage?.LogWarning(result.ToString()); |                     return result; | ||||||
|                 } |                 } | ||||||
|                 success = result.IsSuccess; |  | ||||||
|             } |  | ||||||
|             if (!result.IsSuccess) |  | ||||||
|             { |  | ||||||
|                 return result; |  | ||||||
|             } |             } | ||||||
|  |             return OperResult.Success; | ||||||
|         } |         } | ||||||
|         OperResult operResult = OperResult.Success; |  | ||||||
|         return operResult; |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private ValueTask<OperResult> UpdateAlarmModel(IEnumerable<AlarmVariable> item, CancellationToken cancellationToken) |     private ValueTask<OperResult> UpdateAlarmModel(IEnumerable<AlarmVariable> item, CancellationToken cancellationToken) | ||||||
| @@ -226,37 +232,42 @@ public partial class RabbitMQProducer : BusinessBaseWithCacheIntervalScriptAll | |||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// 上传,返回上传结果 |     /// 上传,返回上传结果 | ||||||
|     /// </summary> |     /// </summary> | ||||||
|     public async Task<OperResult> RabbitMQUpAsync(TopicArray topicArray, CancellationToken cancellationToken) |     public Task<OperResult> RabbitMQUpAsync(TopicArray topicArray, CancellationToken cancellationToken) | ||||||
|     { |     { | ||||||
|         try |         return RabbitMQUpAsync(this, topicArray, cancellationToken); | ||||||
|         { |  | ||||||
|             if (_channel != null) |  | ||||||
|             { |  | ||||||
|                 await _channel.BasicPublishAsync(_driverPropertys.ExchangeName, topicArray.Topic, topicArray.Payload, cancellationToken).ConfigureAwait(false); |  | ||||||
|  |  | ||||||
|                 if (_driverPropertys.DetailLog) |         static async PooledTask<OperResult> RabbitMQUpAsync(RabbitMQProducer @this, TopicArray topicArray, CancellationToken cancellationToken) | ||||||
|  |         { | ||||||
|  |             try | ||||||
|  |             { | ||||||
|  |                 if (@this._channel != null) | ||||||
|                 { |                 { | ||||||
|                     if (LogMessage?.LogLevel <= TouchSocket.Core.LogLevel.Trace) |                     await @this._channel.BasicPublishAsync(@this._driverPropertys.ExchangeName, topicArray.Topic, topicArray.Payload, cancellationToken).ConfigureAwait(false); | ||||||
|                         LogMessage?.LogTrace(GetDetailLogString(topicArray, _memoryVarModelQueue.Count)); |  | ||||||
|                     else if (LogMessage?.LogLevel <= TouchSocket.Core.LogLevel.Debug) |                     if (@this._driverPropertys.DetailLog) | ||||||
|                         LogMessage?.LogDebug(GetCountLogString(topicArray, _memoryVarModelQueue.Count)); |                     { | ||||||
|  |                         if (@this.LogMessage?.LogLevel <= TouchSocket.Core.LogLevel.Trace) | ||||||
|  |                             @this.LogMessage?.LogTrace(@this.GetDetailLogString(topicArray, @this._memoryVarModelQueue.Count)); | ||||||
|  |                         else if (@this.LogMessage?.LogLevel <= TouchSocket.Core.LogLevel.Debug) | ||||||
|  |                             @this.LogMessage?.LogDebug(@this.GetCountLogString(topicArray, @this._memoryVarModelQueue.Count)); | ||||||
|  |                     } | ||||||
|  |                     else | ||||||
|  |                     { | ||||||
|  |                         if (@this.LogMessage?.LogLevel <= TouchSocket.Core.LogLevel.Debug) | ||||||
|  |                             @this.LogMessage?.LogDebug(@this.GetCountLogString(topicArray, @this._memoryVarModelQueue.Count)); | ||||||
|  |                     } | ||||||
|  |                     return OperResult.Success; | ||||||
|                 } |                 } | ||||||
|                 else |                 else | ||||||
|                 { |                 { | ||||||
|                     if (LogMessage?.LogLevel <= TouchSocket.Core.LogLevel.Debug) |                     return new OperResult("Upload fail"); | ||||||
|                         LogMessage?.LogDebug(GetCountLogString(topicArray, _memoryVarModelQueue.Count)); |  | ||||||
|                 } |                 } | ||||||
|                 return OperResult.Success; |  | ||||||
|             } |             } | ||||||
|             else |             catch (Exception ex) | ||||||
|             { |             { | ||||||
|                 return new OperResult("Upload fail"); |                 return new OperResult(ex); | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|         catch (Exception ex) |  | ||||||
|         { |  | ||||||
|             return new OperResult(ex); |  | ||||||
|         } |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     #endregion 方法 |     #endregion 方法 | ||||||
|   | |||||||
| @@ -10,6 +10,8 @@ | |||||||
|  |  | ||||||
| using Newtonsoft.Json.Linq; | using Newtonsoft.Json.Linq; | ||||||
|  |  | ||||||
|  | using PooledAwait; | ||||||
|  |  | ||||||
| using System.Collections.Concurrent; | using System.Collections.Concurrent; | ||||||
|  |  | ||||||
| using ThingsGateway.Debug; | using ThingsGateway.Debug; | ||||||
| @@ -119,74 +121,79 @@ public class SiemensS7Master : CollectFoundationBase | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     protected override async ValueTask<Dictionary<string, OperResult>> WriteValuesAsync(Dictionary<VariableRuntime, JToken> writeInfoLists, CancellationToken cancellationToken) |     protected override ValueTask<Dictionary<string, OperResult>> WriteValuesAsync(Dictionary<VariableRuntime, JToken> writeInfoLists, CancellationToken cancellationToken) | ||||||
|     { |     { | ||||||
|         using var writeLock = await ReadWriteLock.WriterLockAsync(cancellationToken).ConfigureAwait(false); |         return WriteValuesAsync(this, writeInfoLists, cancellationToken); | ||||||
|  |  | ||||||
|         // 检查协议是否为空,如果为空则抛出异常 |         static async PooledValueTask<Dictionary<string, OperResult>> WriteValuesAsync(SiemensS7Master @this, Dictionary<VariableRuntime, JToken> writeInfoLists, CancellationToken cancellationToken) | ||||||
|         if (FoundationDevice == null) |  | ||||||
|             throw new NotSupportedException(); |  | ||||||
|  |  | ||||||
|         // 创建用于存储操作结果的并发字典 |  | ||||||
|         NonBlockingDictionary<string, OperResult> operResults = new(); |  | ||||||
|  |  | ||||||
|         //转换 |  | ||||||
|         Dictionary<VariableRuntime, SiemensS7Address> addresses = new(); |  | ||||||
|         var w1 = writeInfoLists.Where(a => a.Key.DataType != DataTypeEnum.String); |  | ||||||
|         var w2 = writeInfoLists.Where(a => a.Key.DataType == DataTypeEnum.String); |  | ||||||
|         foreach (var item in w1) |  | ||||||
|         { |         { | ||||||
|             SiemensS7Address siemensS7Address = SiemensS7Address.ParseFrom(item.Key.RegisterAddress); |             using var writeLock = await @this.ReadWriteLock.WriterLockAsync(cancellationToken).ConfigureAwait(false); | ||||||
|             siemensS7Address.Data = GetBytes(item.Key.DataType, item.Value); |  | ||||||
|             siemensS7Address.Length = siemensS7Address.Data.Length; |             // 检查协议是否为空,如果为空则抛出异常 | ||||||
|             siemensS7Address.BitLength = 1; |             if (@this.FoundationDevice == null) | ||||||
|             siemensS7Address.IsBit = item.Key.DataType == DataTypeEnum.Boolean; |                 throw new NotSupportedException(); | ||||||
|             if (item.Key.DataType == DataTypeEnum.Boolean) |  | ||||||
|  |             // 创建用于存储操作结果的并发字典 | ||||||
|  |             NonBlockingDictionary<string, OperResult> operResults = new(); | ||||||
|  |  | ||||||
|  |             //转换 | ||||||
|  |             Dictionary<VariableRuntime, SiemensS7Address> addresses = new(); | ||||||
|  |             var w1 = writeInfoLists.Where(a => a.Key.DataType != DataTypeEnum.String); | ||||||
|  |             var w2 = writeInfoLists.Where(a => a.Key.DataType == DataTypeEnum.String); | ||||||
|  |             foreach (var item in w1) | ||||||
|             { |             { | ||||||
|                 if (item.Value is JArray jArray) |                 SiemensS7Address siemensS7Address = SiemensS7Address.ParseFrom(item.Key.RegisterAddress); | ||||||
|  |                 siemensS7Address.Data = @this.GetBytes(item.Key.DataType, item.Value); | ||||||
|  |                 siemensS7Address.Length = siemensS7Address.Data.Length; | ||||||
|  |                 siemensS7Address.BitLength = 1; | ||||||
|  |                 siemensS7Address.IsBit = item.Key.DataType == DataTypeEnum.Boolean; | ||||||
|  |                 if (item.Key.DataType == DataTypeEnum.Boolean) | ||||||
|                 { |                 { | ||||||
|                     siemensS7Address.BitLength = jArray.ToObject<Boolean[]>().Length; |                     if (item.Value is JArray jArray) | ||||||
|  |                     { | ||||||
|  |                         siemensS7Address.BitLength = jArray.ToObject<Boolean[]>().Length; | ||||||
|  |                     } | ||||||
|                 } |                 } | ||||||
|  |                 addresses.Add(item.Key, siemensS7Address); | ||||||
|             } |             } | ||||||
|             addresses.Add(item.Key, siemensS7Address); |             if (addresses.Count > 0) | ||||||
|         } |  | ||||||
|         if (addresses.Count > 0) |  | ||||||
|         { |  | ||||||
|             var result = await _plc.S7WriteAsync(addresses.Select(a => a.Value).ToArray(), cancellationToken).ConfigureAwait(false); |  | ||||||
|             foreach (var writeInfo in addresses) |  | ||||||
|             { |             { | ||||||
|                 if (result.TryGetValue(writeInfo.Value, out var r1)) |                 var result = await @this._plc.S7WriteAsync(addresses.Select(a => a.Value).ToArray(), cancellationToken).ConfigureAwait(false); | ||||||
|  |                 foreach (var writeInfo in addresses) | ||||||
|                 { |                 { | ||||||
|                     operResults.TryAdd(writeInfo.Key.Name, r1); |                     if (result.TryGetValue(writeInfo.Value, out var r1)) | ||||||
|  |                     { | ||||||
|  |                         operResults.TryAdd(writeInfo.Key.Name, r1); | ||||||
|  |                     } | ||||||
|                 } |                 } | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|             } |             } | ||||||
|  |  | ||||||
|  |             // 使用并发方式遍历写入信息列表,并进行异步写入操作 | ||||||
|  |             await w2.ForEachAsync(async (writeInfo) => | ||||||
|  |             { | ||||||
|  |                 try | ||||||
|  |                 { | ||||||
|  |                     // 调用协议的写入方法,将写入信息中的数据写入到对应的寄存器地址,并获取操作结果 | ||||||
|  |                     var result = await @this.FoundationDevice.WriteJTokenAsync(writeInfo.Key.RegisterAddress, writeInfo.Value, writeInfo.Key.DataType, cancellationToken).ConfigureAwait(false); | ||||||
|  |  | ||||||
|  |                     // 将操作结果添加到结果字典中,使用变量名称作为键 | ||||||
|  |                     operResults.TryAdd(writeInfo.Key.Name, result); | ||||||
|  |                 } | ||||||
|  |                 catch (Exception ex) | ||||||
|  |                 { | ||||||
|  |                     operResults.TryAdd(writeInfo.Key.Name, new(ex)); | ||||||
|  |                 } | ||||||
|  |             }).ConfigureAwait(false); | ||||||
|  |  | ||||||
|  |             await @this.Check(writeInfoLists, operResults, cancellationToken).ConfigureAwait(false); | ||||||
|  |             if (@this.LogMessage?.LogLevel <= TouchSocket.Core.LogLevel.Debug) | ||||||
|  |                 @this.LogMessage?.Debug(string.Format("Write result: {0} - {1}", @this.DeviceName, operResults.Select(a => $"{a.Key} - {a.Key.Length} - {(a.Value.IsSuccess ? "Success" : a.Value.ErrorMessage)}").ToSystemTextJsonString(false))); | ||||||
|  |             // 返回包含操作结果的字典 | ||||||
|  |             return new Dictionary<string, OperResult>(operResults); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         // 使用并发方式遍历写入信息列表,并进行异步写入操作 |  | ||||||
|         await w2.ForEachAsync(async (writeInfo) => |  | ||||||
|         { |  | ||||||
|             try |  | ||||||
|             { |  | ||||||
|                 // 调用协议的写入方法,将写入信息中的数据写入到对应的寄存器地址,并获取操作结果 |  | ||||||
|                 var result = await FoundationDevice.WriteJTokenAsync(writeInfo.Key.RegisterAddress, writeInfo.Value, writeInfo.Key.DataType, cancellationToken).ConfigureAwait(false); |  | ||||||
|  |  | ||||||
|                 // 将操作结果添加到结果字典中,使用变量名称作为键 |  | ||||||
|                 operResults.TryAdd(writeInfo.Key.Name, result); |  | ||||||
|             } |  | ||||||
|             catch (Exception ex) |  | ||||||
|             { |  | ||||||
|                 operResults.TryAdd(writeInfo.Key.Name, new(ex)); |  | ||||||
|             } |  | ||||||
|         }).ConfigureAwait(false); |  | ||||||
|  |  | ||||||
|         await Check(writeInfoLists, operResults, cancellationToken).ConfigureAwait(false); |  | ||||||
|         if (LogMessage?.LogLevel <= TouchSocket.Core.LogLevel.Debug) |  | ||||||
|             LogMessage?.Debug(string.Format("Write result: {0} - {1}", DeviceName, operResults.Select(a => $"{a.Key} - {a.Key.Length} - {(a.Value.IsSuccess ? "Success" : a.Value.ErrorMessage)}").ToSystemTextJsonString(false))); |  | ||||||
|         // 返回包含操作结果的字典 |  | ||||||
|         return new Dictionary<string, OperResult>(operResults); |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |  | ||||||
|   | |||||||
| @@ -162,7 +162,7 @@ public class TestCollectPlugin1 : CollectBase | |||||||
|     /// <param name="state"></param> |     /// <param name="state"></param> | ||||||
|     /// <param name="cancellationToken"></param> |     /// <param name="cancellationToken"></param> | ||||||
|     /// <returns></returns> |     /// <returns></returns> | ||||||
|     protected override Task TestOnline(object? state, CancellationToken cancellationToken) |     protected override ValueTask TestOnline(object? state, CancellationToken cancellationToken) | ||||||
|     { |     { | ||||||
|         return base.TestOnline(state, cancellationToken); |         return base.TestOnline(state, cancellationToken); | ||||||
|     } |     } | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 2248356998 qq.com
					2248356998 qq.com