mirror of
https://gitee.com/ThingsGateway/ThingsGateway.git
synced 2025-10-20 10:50:48 +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/>
|
||||||
@@ -531,7 +545,7 @@ public abstract class DeviceBase : AsyncAndSyncDisposableObject, IDevice
|
|||||||
}
|
}
|
||||||
|
|
||||||
private ObjectPoolLock<ReusableCancellationTokenSource> _reusableTimeouts = new();
|
private ObjectPoolLock<ReusableCancellationTokenSource> _reusableTimeouts = new();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 发送并等待数据
|
/// 发送并等待数据
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -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;
|
||||||
@@ -157,7 +159,7 @@ public class ModbusSlave : DeviceBase, IModbusAddress
|
|||||||
{
|
{
|
||||||
return PackHelper.LoadSourceRead<T>(this, deviceVariables, maxPack, defaultIntervalTime);
|
return PackHelper.LoadSourceRead<T>(this, deviceVariables, maxPack, defaultIntervalTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
protected override Task DisposeAsync(bool disposing)
|
protected override Task DisposeAsync(bool disposing)
|
||||||
{
|
{
|
||||||
@@ -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