Compare commits

...

24 Commits

Author SHA1 Message Date
2248356998 qq.com
d71ee29da8 10.11.54 2025-09-17 20:50:41 +08:00
2248356998 qq.com
901aa2d59f 10.11.52 2025-09-17 11:07:45 +08:00
2248356998 qq.com
764957c014 10.11.51 2025-09-16 22:19:01 +08:00
Diego
0e3898218b 10.11.44 2025-09-16 19:13:19 +08:00
Diego
61f13cef3c 10.11.43 2025-09-15 17:35:49 +08:00
2248356998 qq.com
0b663d9e01 fix: modbusRtu 0x10 2025-09-14 11:18:41 +08:00
2248356998 qq.com
6c95c6209f 10.11.41 2025-09-13 14:22:13 +08:00
2248356998 qq.com
4d223d2622 10.11.40 2025-09-13 13:00:52 +08:00
yunqi
e8d7e91b64 !74 modify dlt645_2007 buildcmd
* modify dlt645_2007 buildcmd
2025-09-13 04:59:10 +00:00
2248356998 qq.com
8175f541ec 更新基准测试 2025-09-12 23:53:36 +08:00
2248356998 qq.com
0adbdb926b 更新基准测试 2025-09-12 23:49:18 +08:00
Diego
42adee9980 更新依赖 2025-09-11 22:22:39 +08:00
2248356998 qq.com
427a7404bc 更新UI 2025-09-11 19:05:14 +08:00
2248356998 qq.com
3658199e0a 10.11.37 2025-09-11 18:30:49 +08:00
Diego
82eedee50a 更新依赖 2025-09-11 18:12:40 +08:00
2248356998 qq.com
6a18fc3e06 10.11.36 2025-09-10 12:59:54 +08:00
2248356998 qq.com
c37e314ed6 更新依赖 2025-09-09 21:53:39 +08:00
2248356998 qq.com
a937a85d90 添加项目 2025-09-08 21:51:37 +08:00
2248356998 qq.com
35dd4ae9d3 更新依赖 2025-09-08 21:27:49 +08:00
2248356998 qq.com
0b829ac85c 10.11.33 2025-09-08 21:16:37 +08:00
2248356998 qq.com
aa247422d2 10.11.31 2025-09-08 17:56:19 +08:00
2248356998 qq.com
2e00e8c135 更新依赖 2025-09-08 08:46:52 +08:00
2248356998 qq.com
34dd2cf0a7 更新依赖 2025-09-07 21:52:41 +08:00
2248356998 qq.com
8404e20c5e build: 10.11.27
refactor: 更新依赖
2025-09-06 23:34:32 +08:00
94 changed files with 1771 additions and 635 deletions

3
.gitignore vendored
View File

@@ -364,8 +364,5 @@ FodyWeavers.xsd
/src/*Pro*/
/src/*Pro*
/src/**/*Pro*
/src/*pro*
/src/*pro*/
/src/ThingsGateway.Server/Configuration/GiteeOAuthSettings.json
/src/.idea/

View File

@@ -4,9 +4,9 @@
<div class="tg-table h-100">
<Table TItem="TItem" IsBordered="true" IsStriped="true" TableSize="TableSize.Compact" IsMultipleSelect="IsMultipleSelect" @ref="Instance" SearchTemplate="SearchTemplate"
<Table TItem="TItem" IsBordered="true" IsStriped="true" TableSize="TableSize.Compact" SelectedRows=SelectedRows SelectedRowsChanged=privateSelectedRowsChanged IsMultipleSelect="IsMultipleSelect" @ref="Instance" SearchTemplate="SearchTemplate"
DataService="DataService" CreateItemCallback="CreateItemCallback!"
IsPagination="IsPagination" PageItemsSource="PageItemsSource" IsFixedHeader="IsFixedHeader" IndentSize=24 RowHeight=RowHeight ShowSearchText="ShowSearchText" ShowSearchButton="ShowSearchButton" BeforeShowEditDialogCallback="BeforeShowEditDialogCallback!"
IsPagination="IsPagination" PageItemsSource="PageItemsSource" IsFixedHeader="IsFixedHeader" IndentSize=24 RowHeight=RowHeight ShowSearchText="ShowSearchText" ShowSearchButton="ShowSearchButton" DisableEditButtonCallback="DisableEditButtonCallback" DisableDeleteButtonCallback="DisableDeleteButtonCallback" BeforeShowEditDialogCallback=" BeforeShowEditDialogCallback!"
IsTree="IsTree" OnTreeExpand="OnTreeExpand!" TreeNodeConverter="TreeNodeConverter!" TreeIcon="fa-solid fa-circle-chevron-right" TreeExpandIcon="fa-solid fa-circle-chevron-right fa-rotate-90" IsAutoQueryFirstRender=IsAutoQueryFirstRender
ShowDefaultButtons="ShowDefaultButtons" ShowAdvancedSearch="ShowAdvancedSearch" ShowResetButton=ShowResetButton
ShowEmpty="ShowEmpty" EmptyText="@EmptyText" EmptyImage="@($"{WebsiteConst.DefaultResourceUrl}images/empty.svg")" SortString="@SortString" EditDialogSize="EditDialogSize"
@@ -14,7 +14,7 @@
ShowSkeleton="true" ShowLoading="ShowLoading" ShowSearch="ShowSearch" SearchModel=@SearchModel ShowLineNo
SearchMode=SearchMode ShowExportPdfButton=ShowExportPdfButton ExportButtonText=@ExportButtonText
ShowExportButton=@ShowExportButton Items=Items ClickToSelect=ClickToSelect ScrollMode=ScrollMode
ShowExportCsvButton=@ShowExportCsvButton SelectedRowsChanged=SelectedRowsChanged ShowCardView=ShowCardView
ShowExportCsvButton=@ShowExportCsvButton ShowCardView=ShowCardView
FixedExtendButtonsColumn=FixedExtendButtonsColumn FixedMultipleColumn=FixedMultipleColumn FixedDetailRowHeaderColumn=FixedDetailRowHeaderColumn FixedLineNoColumn=FixedLineNoColumn
IsAutoRefresh=IsAutoRefresh AutoRefreshInterval=AutoRefreshInterval
AllowDragColumn=@AllowDragColumn Height=@Height ShowRefresh=ShowRefresh
@@ -29,7 +29,7 @@
ShowMultiFilterHeader=ShowMultiFilterHeader
ShowFilterHeader=ShowFilterHeader
ShowColumnList=ShowColumnList ExtendButtonColumnWidth="@ExtendButtonColumnWidth"
CustomerSearchModel="CustomerSearchModel" SelectedRows="SelectedRows" ModelEqualityComparer="ModelEqualityComparer!"
CustomerSearchModel="CustomerSearchModel" ModelEqualityComparer="ModelEqualityComparer!"
ShowExtendEditButtonCallback="ShowExtendEditButtonCallback!" ShowExtendDeleteButtonCallback="ShowExtendDeleteButtonCallback!"
DisableExtendEditButton="DisableExtendEditButton!" DisableExtendDeleteButton="DisableExtendDeleteButton!"
DisableExtendEditButtonCallback="DisableExtendEditButtonCallback!" DisableExtendDeleteButtonCallback="DisableExtendDeleteButtonCallback!"

View File

@@ -13,6 +13,24 @@ namespace ThingsGateway.Admin.Razor;
[CascadingTypeParameter(nameof(TItem))]
public partial class AdminTable<TItem> where TItem : class, new()
{
/// <inheritdoc cref="Table{TItem}.SelectedRowsChanged"/>
[Parameter]
public EventCallback<List<TItem>> SelectedRowsChanged { get; set; }
/// <inheritdoc cref="Table{TItem}.SelectedRows"/>
[Parameter]
public List<TItem> SelectedRows { get; set; } = new();
private async Task privateSelectedRowsChanged(List<TItem> items)
{
SelectedRows = items;
if (SelectedRowsChanged.HasDelegate)
await SelectedRowsChanged.InvokeAsync(items);
}
/// <inheritdoc cref="Table{TItem}.DoubleClickToEdit"/>
[Parameter]
public bool DoubleClickToEdit { get; set; } = false;
@@ -210,14 +228,6 @@ public partial class AdminTable<TItem> where TItem : class, new()
[Parameter]
public RenderFragment<TItem>? SearchTemplate { get; set; }
/// <inheritdoc cref="Table{TItem}.SelectedRows"/>
[Parameter]
public List<TItem>? SelectedRows { get; set; } = new List<TItem>();
/// <inheritdoc cref="Table{TItem}.SelectedRowsChanged"/>
[Parameter]
public EventCallback<List<TItem>> SelectedRowsChanged { get; set; }
/// <inheritdoc cref="Table{TItem}.SetRowClassFormatter"/>
[Parameter]
public Func<TItem, string?>? SetRowClassFormatter { get; set; }
@@ -266,6 +276,15 @@ public partial class AdminTable<TItem> where TItem : class, new()
[Parameter]
public bool ShowExportButton { get; set; } = false;
/// <inheritdoc cref="Table{TItem}.DisableEditButtonCallback"/>
public Func<List<TItem>, bool> DisableEditButtonCallback { get; set; } = (list) =>
list.Count != 1;
/// <inheritdoc cref="Table{TItem}.DisableDeleteButtonCallback"/>
[Parameter]
public Func<List<TItem>, bool> DisableDeleteButtonCallback { get; set; } = (list) =>
list.Count <= 0;
/// <inheritdoc cref="Table{TItem}.ShowExportCsvButton"/>
[Parameter]
public bool ShowExportCsvButton { get; set; } = false;

View File

@@ -14,7 +14,7 @@
<ItemGroup>
<PackageReference Include="BootstrapBlazor.TableExport" Version="9.2.6" />
<PackageReference Include="Yitter.IdGenerator" Version="1.0.14" />
<PackageReference Include="BootstrapBlazor" Version="9.10.0" />
<PackageReference Include="BootstrapBlazor" Version="9.10.1" />
</ItemGroup>
<ItemGroup>

View File

@@ -1,5 +1,4 @@
using System.Collections.Concurrent;
using System.Diagnostics;
using ThingsGateway.NewLife.Log;
using ThingsGateway.NewLife.Reflection;
@@ -64,7 +63,10 @@ public class ObjectPool<T> : DisposeBase, IPool<T> where T : notnull
// 启动定期清理的定时器
StartTimer();
}
~ObjectPool()
{
this.TryDispose();
}
/// <summary>销毁</summary>
/// <param name="disposing"></param>
protected override void Dispose(Boolean disposing)
@@ -73,7 +75,7 @@ public class ObjectPool<T> : DisposeBase, IPool<T> where T : notnull
_timer.TryDispose();
WriteLog($"Dispose {typeof(T).FullName} FreeCount={FreeCount:n0} BusyCount={BusyCount:n0} Total={Total:n0}");
WriteLog($"Dispose {typeof(T).FullName} FreeCount={FreeCount:n0} BusyCount={BusyCount:n0}");
Clear();
}
@@ -109,10 +111,6 @@ public class ObjectPool<T> : DisposeBase, IPool<T> where T : notnull
/// <returns></returns>
public virtual T Get()
{
var sw = Log == null || Log == Logger.Null ? null : Stopwatch.StartNew();
Interlocked.Increment(ref _Total);
var success = false;
Item? pi = null;
do
{
@@ -120,8 +118,6 @@ public class ObjectPool<T> : DisposeBase, IPool<T> where T : notnull
if (_free.TryPop(out pi) || _free2.TryDequeue(out pi))
{
Interlocked.Decrement(ref _FreeCount);
success = true;
}
else
{
@@ -147,8 +143,6 @@ public class ObjectPool<T> : DisposeBase, IPool<T> where T : notnull
WriteLog("Acquire Create Free={0} Busy={1}", FreeCount, count + 1);
#endif
Interlocked.Increment(ref _NewCount);
success = false;
}
// 借出时如果不可用,再次借取
@@ -161,17 +155,6 @@ public class ObjectPool<T> : DisposeBase, IPool<T> where T : notnull
_busy.TryAdd(pi.Value, pi);
Interlocked.Increment(ref _BusyCount);
if (success) Interlocked.Increment(ref _Success);
if (sw != null)
{
sw.Stop();
var ms = sw.Elapsed.TotalMilliseconds;
if (Cost < 0.001)
Cost = ms;
else
Cost = (Cost * 3 + ms) / 4;
}
return pi.Value;
}
@@ -197,7 +180,6 @@ public class ObjectPool<T> : DisposeBase, IPool<T> where T : notnull
#if DEBUG
WriteLog("Return Error");
#endif
Interlocked.Increment(ref _ReleaseCount);
return false;
}
@@ -207,13 +189,11 @@ public class ObjectPool<T> : DisposeBase, IPool<T> where T : notnull
// 是否可用
if (!OnReturn(value))
{
Interlocked.Increment(ref _ReleaseCount);
return false;
}
if (value is DisposeBase db && db.Disposed)
{
Interlocked.Increment(ref _ReleaseCount);
return false;
}
@@ -370,39 +350,14 @@ public class ObjectPool<T> : DisposeBase, IPool<T> where T : notnull
}
}
var ncount = _NewCount;
var fcount = _ReleaseCount;
if (count > 0 || ncount > 0 || fcount > 0)
if (count > 0)
{
Interlocked.Add(ref _NewCount, -ncount);
Interlocked.Add(ref _ReleaseCount, -fcount);
var p = Total == 0 ? 0 : (Double)Success / Total;
WriteLog("Release New={6:n0} Release={7:n0} Free={0} Busy={1} 清除过期资源 {2:n0} 项。总请求 {3:n0} 次,命中 {4:p2},平均 {5:n2}us", FreeCount, BusyCount, count, Total, p, Cost * 1000, ncount, fcount);
WriteLog("Release New={6:n0} Release={7:n0} Free={0} Busy={1} 清除过期资源 {2:n0} 项。", FreeCount, BusyCount, count);
}
}
#endregion
#region
private Int32 _Total;
/// <summary>总请求数</summary>
public Int32 Total => _Total;
private Int32 _Success;
/// <summary>成功数</summary>
public Int32 Success => _Success;
/// <summary>新创建数</summary>
private Int32 _NewCount;
/// <summary>释放数</summary>
private Int32 _ReleaseCount;
/// <summary>平均耗时。单位ms</summary>
private Double Cost;
#endregion
#region
/// <summary>日志</summary>
public ILog Log { get; set; } = Logger.Null;

View File

@@ -5,10 +5,10 @@ namespace ThingsGateway.NewLife;
public class ExpiringDictionary<TKey, TValue> : IDisposable
{
private readonly ConcurrentDictionary<TKey, TValue> _dict = new();
private ConcurrentDictionary<TKey, TValue> _dict = new();
private readonly TimerX _cleanupTimer;
public ExpiringDictionary(int cleanupInterval = 600000)
public ExpiringDictionary(int cleanupInterval = 60000)
{
_cleanupTimer = new TimerX(Clear, null, cleanupInterval, cleanupInterval) { Async = true };
}
@@ -33,11 +33,13 @@ public class ExpiringDictionary<TKey, TValue> : IDisposable
public bool TryRemove(TKey key) => _dict.TryRemove(key, out _);
public void Clear() => _dict.Clear();
public void Clear() => Clear(null);
private void Clear(object? state)
{
_dict.Clear();
var data = _dict;
_dict = new();
data.Clear();
}
public void Dispose()

View File

@@ -106,6 +106,17 @@ public static class Runtime
#if NET6_0_OR_GREATER
public static Boolean IsSystemd
{
get
{
var id = Environment.GetEnvironmentVariable("INVOCATION_ID");
return !string.IsNullOrEmpty(id);
}
}
public static Boolean? isLegacyWindows;
/// <summary>

View File

@@ -8,6 +8,8 @@
// QQ群605534569
//------------------------------------------------------------------------------
using ThingsGateway.NewLife.Log;
namespace ThingsGateway.NewLife;
/// <summary>
@@ -50,24 +52,21 @@ public sealed class WaitLock : IDisposable
public int CurrentCount => _waiterLock.CurrentCount;
public bool Waitting => _waiterLock.CurrentCount < MaxCount;
private object m_lockObj = new();
/// <summary>
/// 离开锁
/// </summary>
public void Release()
{
if (DisposedValue) return;
lock (m_lockObj)
//if (Waitting)
{
if (Waitting)
try
{
try
{
_waiterLock.Release();
}
catch (SemaphoreFullException)
{
}
_waiterLock.Release();
}
catch (SemaphoreFullException)
{
XTrace.WriteException(new Exception($"WaitLock {_name} 释放失败,当前信号量无需释放"));
}
}
}

View File

@@ -554,10 +554,7 @@ public static class Reflect
//}
private static class DelegateCache<TFunc>
{
public static readonly ExpiringDictionary<DelegateCacheKey, TFunc> Cache = new();
}
/// <summary>把一个方法转为泛型委托,便于快速反射调用</summary>
/// <typeparam name="TFunc"></typeparam>
@@ -580,38 +577,42 @@ public static class Reflect
return func;
}
private readonly struct DelegateCacheKey : IEquatable<DelegateCacheKey>
#endregion
}
public static class DelegateCache<TFunc>
{
public static readonly ExpiringDictionary<DelegateCacheKey, TFunc> Cache = new();
}
public readonly struct DelegateCacheKey : IEquatable<DelegateCacheKey>
{
public readonly MethodInfo Method;
public readonly Type FuncType;
public readonly object? Target;
public DelegateCacheKey(MethodInfo method, Type funcType, object? target)
{
public readonly MethodInfo Method;
public readonly Type FuncType;
public readonly object? Target;
Method = method;
FuncType = funcType;
Target = target;
}
public DelegateCacheKey(MethodInfo method, Type funcType, object? target)
public bool Equals(DelegateCacheKey other) =>
Method.Equals(other.Method)
&& FuncType.Equals(other.FuncType)
&& ReferenceEquals(Target, other.Target);
public override bool Equals(object? obj) =>
obj is DelegateCacheKey other && Equals(other);
public override int GetHashCode()
{
unchecked
{
Method = method;
FuncType = funcType;
Target = target;
}
public bool Equals(DelegateCacheKey other) =>
Method.Equals(other.Method)
&& FuncType.Equals(other.FuncType)
&& ReferenceEquals(Target, other.Target);
public override bool Equals(object? obj) =>
obj is DelegateCacheKey other && Equals(other);
public override int GetHashCode()
{
unchecked
{
int hash = Method.GetHashCode();
hash = (hash * 397) ^ FuncType.GetHashCode();
if (Target != null)
hash = (hash * 397) ^ RuntimeHelpers.GetHashCode(Target); // 不受对象重写 GetHashCode 影响
return hash;
}
int hash = Method.GetHashCode();
hash = (hash * 397) ^ FuncType.GetHashCode();
if (Target != null)
hash = (hash * 397) ^ RuntimeHelpers.GetHashCode(Target); // 不受对象重写 GetHashCode 影响
return hash;
}
}
#endregion
}
}

View File

@@ -21,6 +21,7 @@ public class Setting : Config<Setting>
#region
/// <summary>是否启用全局调试。默认启用</summary>
[Description("全局调试。XTrace.Debug")]
[XmlIgnore, IgnoreDataMember]
public Boolean Debug { get; set; } = true;
/// <summary>日志等级只输出大于等于该级别的日志All/Debug/Info/Warn/Error/Fatal默认Info</summary>
@@ -30,6 +31,7 @@ public class Setting : Config<Setting>
/// <summary>文件日志目录。默认Log子目录</summary>
[Description("文件日志目录。默认Log子目录")]
[XmlIgnore, IgnoreDataMember]
public String LogPath { get; set; } = "Logs/XLog";
/// <summary>日志文件上限。超过上限后拆分新日志文件默认5MB0表示不限制大小</summary>
@@ -42,6 +44,7 @@ public class Setting : Config<Setting>
/// <summary>日志文件格式。默认{0:yyyy_MM_dd}.log支持日志等级如 {1}_{0:yyyy_MM_dd}.log</summary>
[Description("日志文件格式。默认{0:yyyy_MM_dd}.log支持日志等级如 {1}_{0:yyyy_MM_dd}.log")]
[XmlIgnore, IgnoreDataMember]
public String LogFileFormat { get; set; } = "{0:yyyy_MM_dd}.log";
/// <summary>日志行格式。默认Time|ThreadId|Kind|Name|Message还支持Level</summary>

View File

@@ -52,7 +52,7 @@
<ItemGroup>
<PackageReference Include="System.ComponentModel.Annotations" Version="5.0.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.4" />
</ItemGroup>

View File

@@ -1,6 +1,7 @@
using System.Reflection;
using ThingsGateway.NewLife.Log;
using ThingsGateway.NewLife.Reflection;
namespace ThingsGateway.NewLife.Threading;
@@ -389,6 +390,13 @@ public class TimerX : ITimer, ITimerx, IDisposable
// 释放非托管资源
Scheduler?.Remove(this, disposing ? "Dispose" : "GC");
DelegateCache<TimerCallback>.Cache.Clear();
#if NET6_0_OR_GREATER
DelegateCache<Func<Object?, ValueTask>>.Cache.Clear();
#endif
DelegateCache<Func<Object?, Task>>.Cache.Clear();
}
#if NET6_0_OR_GREATER

View File

@@ -1,7 +1,7 @@
@namespace ThingsGateway.Razor
@typeparam TItem
<Table TItem="TItem" IsBordered="true" IsStriped="true" IsMultipleSelect="IsMultipleSelect" @ref="Instance" TableSize=TableSize SearchTemplate=SearchTemplate
<Table TItem="TItem" IsBordered="true" IsStriped="true" IsMultipleSelect="IsMultipleSelect" @ref="Instance" TableSize=TableSize SearchTemplate=SearchTemplate SelectedRows=SelectedRows SelectedRowsChanged=privateSelectedRowsChanged
IsPagination="IsPagination" PageItemsSource="PageItemsSource" IsFixedHeader="IsFixedHeader" IndentSize=24 RowHeight=RowHeight ShowSearchText="ShowSearchText"
IsTree="IsTree" OnTreeExpand="OnTreeExpand!" TreeNodeConverter="TreeNodeConverter!" TreeIcon="fa-solid fa-circle-chevron-right" TreeExpandIcon="fa-solid fa-circle-chevron-right fa-rotate-90" IsAutoQueryFirstRender=IsAutoQueryFirstRender
ShowDefaultButtons="ShowDefaultButtons" ShowAdvancedSearch="ShowAdvancedSearch" ShowResetButton=ShowResetButton
@@ -10,7 +10,7 @@
ShowSkeleton="true" ShowLoading="ShowLoading" ShowSearch="ShowSearch" SearchModel=@SearchModel ShowLineNo
SearchMode=SearchMode ShowExportPdfButton=ShowExportPdfButton
ShowExportButton=@ShowExportButton Items=Items ClickToSelect=ClickToSelect ScrollMode=ScrollMode
ShowExportCsvButton=@ShowExportCsvButton SelectedRowsChanged=SelectedRowsChanged ShowCardView=ShowCardView
ShowExportCsvButton=@ShowExportCsvButton ShowCardView=ShowCardView
FixedExtendButtonsColumn IsAutoRefresh=IsAutoRefresh AutoRefreshInterval=AutoRefreshInterval
AllowDragColumn=@AllowDragColumn Height=@Height ShowRefresh=ShowRefresh
AllowResizing=@AllowResizing ExportButtonDropdownTemplate=ExportButtonDropdownTemplate
@@ -24,7 +24,7 @@
ShowMultiFilterHeader=ShowMultiFilterHeader
ShowFilterHeader=ShowFilterHeader
ShowColumnList=ShowColumnList ExtendButtonColumnWidth="@ExtendButtonColumnWidth"
CustomerSearchModel="CustomerSearchModel" SelectedRows="SelectedRows" ModelEqualityComparer="ModelEqualityComparer!"
CustomerSearchModel="CustomerSearchModel" ModelEqualityComparer="ModelEqualityComparer!"
ShowExtendEditButtonCallback="ShowExtendEditButtonCallback!" ShowExtendDeleteButtonCallback="ShowExtendDeleteButtonCallback!"
DisableExtendEditButton="DisableExtendEditButton!" DisableExtendDeleteButton="DisableExtendDeleteButton!"
DisableExtendEditButtonCallback="DisableExtendEditButtonCallback!" DisableExtendDeleteButtonCallback="DisableExtendDeleteButtonCallback!"

View File

@@ -13,6 +13,23 @@ namespace ThingsGateway.Razor;
[CascadingTypeParameter(nameof(TItem))]
public partial class DefaultTable<TItem> where TItem : class, new()
{
/// <inheritdoc cref="Table{TItem}.SelectedRowsChanged"/>
[Parameter]
public EventCallback<List<TItem>> SelectedRowsChanged { get; set; }
/// <inheritdoc cref="Table{TItem}.SelectedRows"/>
[Parameter]
public List<TItem> SelectedRows { get; set; } = new();
private async Task privateSelectedRowsChanged(List<TItem> items)
{
SelectedRows = items;
if (SelectedRowsChanged.HasDelegate)
await SelectedRowsChanged.InvokeAsync(items);
}
/// <inheritdoc cref="Table{TItem}.AllowDragColumn"/>
[Parameter]
public bool AllowDragColumn { get; set; } = false;
@@ -186,14 +203,6 @@ public partial class DefaultTable<TItem> where TItem : class, new()
[Parameter]
public RenderFragment<TItem>? SearchTemplate { get; set; }
/// <inheritdoc cref="Table{TItem}.SelectedRows"/>
[Parameter]
public List<TItem>? SelectedRows { get; set; } = new List<TItem>();
/// <inheritdoc cref="Table{TItem}.SelectedRowsChanged"/>
[Parameter]
public EventCallback<List<TItem>> SelectedRowsChanged { get; set; }
/// <inheritdoc cref="Table{TItem}.SetRowClassFormatter"/>
[Parameter]
public Func<TItem, string?>? SetRowClassFormatter { get; set; }

View File

@@ -24,15 +24,15 @@
<ItemGroup>
<PackageReference Include="SqlSugarCore.Dm" Version="8.8.2" />
<PackageReference Include="SqlSugarCore.Kdbndp" Version="9.3.7.821" />
<PackageReference Include="SqlSugarCore.Kdbndp" Version="9.3.7.905" />
<PackageReference Include="Microsoft.Data.Sqlite" Version="7.0.20" />
<!--<PackageReference Include="Microsoft.Data.Sqlite" Version="$(NET9Version)" />-->
<PackageReference Include="MySqlConnector" Version="2.4.0" />
<PackageReference Include="Npgsql" Version="9.0.3" />
<PackageReference Include="CsvHelper" Version="33.1.0" />
<PackageReference Include="TDengine.Connector" Version="3.1.8" />
<PackageReference Include="TDengine.Connector" Version="3.1.9" />
<PackageReference Include="Oracle.ManagedDataAccess.Core" Version="23.9.1" />
<PackageReference Include="Oscar.Data.SqlClient" Version="4.2.23" />
<PackageReference Include="Oscar.Data.SqlClient" Version="4.2.25" />
<PackageReference Include="System.Data.Common" Version="4.3.0" />
<PackageReference Include="Microsoft.Data.SqlClient" Version="6.1.1" />
<PackageReference Include="System.Reflection.Emit.Lightweight" Version="4.7.0" />

View File

@@ -1,17 +1,17 @@
<Project>
<PropertyGroup>
<PluginVersion>10.11.26</PluginVersion>
<ProPluginVersion>10.11.26</ProPluginVersion>
<DefaultVersion>10.11.26</DefaultVersion>
<AuthenticationVersion>10.11.3</AuthenticationVersion>
<SourceGeneratorVersion>10.11.3</SourceGeneratorVersion>
<NET8Version>8.0.19</NET8Version>
<NET9Version>9.0.8</NET9Version>
<PluginVersion>10.11.54</PluginVersion>
<ProPluginVersion>10.11.54</ProPluginVersion>
<DefaultVersion>10.11.54</DefaultVersion>
<AuthenticationVersion>10.11.5</AuthenticationVersion>
<SourceGeneratorVersion>10.11.4</SourceGeneratorVersion>
<NET8Version>8.0.20</NET8Version>
<NET9Version>9.0.9</NET9Version>
<SatelliteResourceLanguages>zh-Hans;en-US</SatelliteResourceLanguages>
<IsTrimmable>false</IsTrimmable>
<ManagementProPluginVersion>10.11.22</ManagementProPluginVersion>
<ManagementPluginVersion>10.11.22</ManagementPluginVersion>
<ManagementProPluginVersion>10.11.36</ManagementProPluginVersion>
<ManagementPluginVersion>10.11.36</ManagementPluginVersion>
</PropertyGroup>
<PropertyGroup>

View File

@@ -68,6 +68,7 @@ public class ModbusMasterDemo
bytes = await device.ModbusReadAsync(new ModbusAddress()
{
Station = 1,
StartAddress = 0,
FunctionCode = 3,
Length = 10,
@@ -92,6 +93,7 @@ public class ModbusMasterDemo
write = await device.ModbusRequestAsync(new ModbusAddress()
{
Station = 1,
StartAddress = 0,
FunctionCode = 3,
MasterWriteDatas = device.ThingsGatewayBitConverter.GetBytes(new double[] { 123.456, 123.456 })

View File

@@ -49,12 +49,11 @@ public class DDPTcpSessionClientChannel : TcpSessionClientChannel
this.ThrowIfDisposed();
this.ThrowIfClientNotConnected();
if (!await this.OnTcpSending(memory).ConfigureAwait(false)) return;
var transport = this.Transport;
var adapter = this.DataHandlingAdapter;
var locker = transport.SemaphoreSlimForWriter;
var locker = transport.WriteLocker;
await locker.WaitAsync(token).ConfigureAwait(false);
try
@@ -62,7 +61,7 @@ public class DDPTcpSessionClientChannel : TcpSessionClientChannel
// 如果数据处理适配器未设置,则使用默认发送方式。
if (adapter == null)
{
await transport.Output.WriteAsync(memory, token).ConfigureAwait(false);
await transport.Writer.WriteAsync(memory, token).ConfigureAwait(false);
}
else
{
@@ -70,7 +69,7 @@ public class DDPTcpSessionClientChannel : TcpSessionClientChannel
var ddpSend = new DDPSend(memory, Id, true);
ddpSend.Build(ref byteBlock);
var newMemory = byteBlock.Memory;
var writer = new PipeBytesWriter(transport.Output);
var writer = new PipeBytesWriter(transport.Writer);
adapter.SendInput(ref writer, in newMemory);
await writer.FlushAsync(token).ConfigureAwait(false);
}
@@ -100,7 +99,7 @@ public class DDPTcpSessionClientChannel : TcpSessionClientChannel
var transport = this.Transport;
var adapter = this.DataHandlingAdapter;
var locker = transport.SemaphoreSlimForWriter;
var locker = transport.WriteLocker;
await locker.WaitAsync(token).ConfigureAwait(false);
try
@@ -113,7 +112,7 @@ public class DDPTcpSessionClientChannel : TcpSessionClientChannel
requestInfoBuilder.Build(ref byteBlock);
var ddpSend = new DDPSend(byteBlock.Memory, Id, true);
var writer = new PipeBytesWriter(transport.Output);
var writer = new PipeBytesWriter(transport.Writer);
adapter.SendInput(ref writer, ddpSend);
await writer.FlushAsync(token).ConfigureAwait(false);
}

View File

@@ -37,9 +37,8 @@ public static class ChannelOptionsExtensions
for (int i = 0; i < funcs.Count; i++)
{
var func = funcs[i];
var task = func.Invoke(clientChannel, e, i == funcs.Count - 1);
if (!task.IsCompleted)
await task.ConfigureAwait(false);
if (func == null) continue;
await func.Invoke(clientChannel, e, i == funcs.Count - 1).ConfigureAwait(false);
if (e.Handled)
{
break;
@@ -67,6 +66,7 @@ public static class ChannelOptionsExtensions
for (int i = 0; i < funcs.Count; i++)
{
var func = funcs[i];
if (func == null) continue;
var handled = await func.Invoke(clientChannel, i == funcs.Count - 1).ConfigureAwait(false);
if (handled)
{
@@ -98,19 +98,32 @@ public static class ChannelOptionsExtensions
if (channelOptions.MaxClientCount > 0)
config.SetMaxCount(channelOptions.MaxClientCount);
config.SetTransportOption(new TouchSocket.Sockets.TransportOption()
{
SendPipeOptions = new System.IO.Pipelines.PipeOptions(
minimumSegmentSize: 1024,
useSynchronizationContext: false),
ReceivePipeOptions = new System.IO.Pipelines.PipeOptions(
minimumSegmentSize: 1024,
pauseWriterThreshold: 1024 * 1024,
resumeWriterThreshold: 1024 * 512,
useSynchronizationContext: false)
});
switch (channelType)
{
case ChannelTypeEnum.TcpClient:
return config.GetTcpClientWithIPHost(channelOptions);
return config.GetTcpClient(channelOptions);
case ChannelTypeEnum.TcpService:
return config.GetTcpServiceWithBindIPHost(channelOptions);
return config.GetTcpService(channelOptions);
case ChannelTypeEnum.SerialPort:
return config.GetSerialPortWithOption(channelOptions);
return config.GetSerialPort(channelOptions);
case ChannelTypeEnum.UdpSession:
return config.GetUdpSessionWithIPHost(channelOptions);
return config.GetUdpSession(channelOptions);
case ChannelTypeEnum.Other:
channelOptions.Config = config;
OtherChannel otherChannel = new OtherChannel(channelOptions);
@@ -125,13 +138,12 @@ public static class ChannelOptionsExtensions
/// <param name="config">配置</param>
/// <param name="channelOptions">串口配置</param>
/// <returns></returns>
public static SerialPortChannel GetSerialPortWithOption(this TouchSocketConfig config, IChannelOptions channelOptions)
private static SerialPortChannel GetSerialPort(this TouchSocketConfig config, IChannelOptions channelOptions)
{
var serialPortOption = channelOptions.Map<SerialPortOption>();
serialPortOption.ThrowIfNull(nameof(SerialPortOption));
channelOptions.Config = config;
config.SetSerialPortOption(serialPortOption);
//载入配置
SerialPortChannel serialPortChannel = new SerialPortChannel(channelOptions);
return serialPortChannel;
@@ -144,7 +156,7 @@ public static class ChannelOptionsExtensions
/// <param name="channelOptions">通道配置</param>
/// <returns></returns>
/// <exception cref="ArgumentNullException"></exception>
public static TcpClientChannel GetTcpClientWithIPHost(this TouchSocketConfig config, IChannelOptions channelOptions)
private static TcpClientChannel GetTcpClient(this TouchSocketConfig config, IChannelOptions channelOptions)
{
var remoteUrl = channelOptions.RemoteUrl;
var bindUrl = channelOptions.BindUrl;
@@ -166,7 +178,7 @@ public static class ChannelOptionsExtensions
/// <param name="channelOptions">通道配置</param>
/// <returns></returns>
/// <exception cref="ArgumentNullException"></exception>
public static IChannel GetTcpServiceWithBindIPHost(this TouchSocketConfig config, IChannelOptions channelOptions)
private static IChannel GetTcpService(this TouchSocketConfig config, IChannelOptions channelOptions)
{
var bindUrl = channelOptions.BindUrl;
bindUrl.ThrowIfNull(nameof(bindUrl));
@@ -194,7 +206,7 @@ public static class ChannelOptionsExtensions
/// <param name="channelOptions">通道配置</param>
/// <returns></returns>
/// <exception cref="ArgumentNullException"></exception>
public static UdpSessionChannel GetUdpSessionWithIPHost(this TouchSocketConfig config, IChannelOptions channelOptions)
private static UdpSessionChannel GetUdpSession(this TouchSocketConfig config, IChannelOptions channelOptions)
{
var remoteUrl = channelOptions.RemoteUrl;
var bindUrl = channelOptions.BindUrl;

View File

@@ -57,7 +57,7 @@ public interface IChannel : ISetupConfigObject, IDisposable, IClosableClient, IC
/// </summary>
public ChannelEventHandler Stoping { get; }
void ResetSign(int minSign = 0, int maxSign = ushort.MaxValue);
void ResetSign(int minSign = 1, int maxSign = ushort.MaxValue - 1);
}

View File

@@ -30,6 +30,7 @@ public interface IClientChannel : IChannel, ISender, IClient, IClientSender, IOn
WaitHandlePool<MessageBase> WaitHandlePool { get; }
WaitLock GetLock(string key);
void LogSeted(bool logSeted);
/// <summary>
/// 设置数据处理适配器

View File

@@ -19,6 +19,11 @@ namespace ThingsGateway.Foundation;
/// </summary>
public class OtherChannel : SetupConfigObject, IClientChannel
{
~OtherChannel()
{
this.SafeDispose();
}
private SingleStreamDataHandlingAdapter m_dataHandlingAdapter;
public DataHandlingAdapter ReadOnlyDataHandlingAdapter => m_dataHandlingAdapter;
@@ -29,19 +34,9 @@ public class OtherChannel : SetupConfigObject, IClientChannel
}
public override TouchSocketConfig Config => base.Config ?? ChannelOptions.Config;
public void SetDataHandlingAdapterLogger(ILog log)
{
if (_deviceDataHandleAdapter != ReadOnlyDataHandlingAdapter && ReadOnlyDataHandlingAdapter is IDeviceDataHandleAdapter handleAdapter)
{
_deviceDataHandleAdapter = handleAdapter;
}
if (_deviceDataHandleAdapter != null)
{
_deviceDataHandleAdapter.Logger = log;
}
}
public void ResetSign(int minSign = 0, int maxSign = ushort.MaxValue)
public void ResetSign(int minSign = 1, int maxSign = ushort.MaxValue - 1)
{
var pool = WaitHandlePool;
WaitHandlePool = new WaitHandlePool<MessageBase>(minSign, maxSign);
@@ -73,7 +68,7 @@ public class OtherChannel : SetupConfigObject, IClientChannel
/// <summary>
/// 等待池
/// </summary>
public WaitHandlePool<MessageBase> WaitHandlePool { get; internal set; } = new(0, ushort.MaxValue);
public WaitHandlePool<MessageBase> WaitHandlePool { get; internal set; } = new(1, ushort.MaxValue - 1);
/// <inheritdoc/>
public WaitLock WaitLock => ChannelOptions.WaitLock;
@@ -83,15 +78,29 @@ public class OtherChannel : SetupConfigObject, IClientChannel
//private readonly WaitLock _connectLock = new WaitLock();
private IDeviceDataHandleAdapter _deviceDataHandleAdapter;
private bool logSet;
/// <inheritdoc/>
public void SetDataHandlingAdapterLogger(ILog log)
{
if (!logSet && ReadOnlyDataHandlingAdapter is IDeviceDataHandleAdapter handleAdapter)
{
logSet = true;
handleAdapter.Logger = log;
}
}
/// <inheritdoc/>
public void SetDataHandlingAdapter(DataHandlingAdapter adapter)
{
if (adapter is SingleStreamDataHandlingAdapter singleStreamDataHandlingAdapter)
SetAdapter(singleStreamDataHandlingAdapter);
if (adapter is IDeviceDataHandleAdapter deviceDataHandleAdapter)
_deviceDataHandleAdapter = deviceDataHandleAdapter;
logSet = false;
}
public void LogSeted(bool logSeted)
{
logSet = logSeted;
}
/// <summary>
/// 设置数据处理适配器。

View File

@@ -34,7 +34,26 @@ public static class PluginUtil
if (channelOptions.ChannelType == ChannelTypeEnum.TcpClient)
{
action += a => a.UseReconnection<IClientChannel>();
action += a => a.UseReconnection<IClientChannel>().SetActionForCheck((channel, failCount) =>
{
if (channel.Online)
{
return Task.FromResult(ConnectionCheckResult.Alive);
}
else
{
if (failCount > 1)
{
return Task.FromResult(ConnectionCheckResult.Dead);
}
return Task.FromResult(ConnectionCheckResult.Skip);
}
})
.SetPollingTick(TimeSpan.FromSeconds(5)
);
}
return action;
}

View File

@@ -19,6 +19,10 @@ namespace ThingsGateway.Foundation;
/// </summary>
public class SerialPortChannel : SerialPortClient, IClientChannel
{
~SerialPortChannel()
{
this.SafeDispose();
}
public SerialPortChannel(IChannelOptions channelOptions)
{
ChannelOptions = channelOptions;
@@ -27,7 +31,7 @@ public class SerialPortChannel : SerialPortClient, IClientChannel
public override TouchSocketConfig Config => base.Config ?? ChannelOptions.Config;
public void ResetSign(int minSign = 0, int maxSign = ushort.MaxValue)
public void ResetSign(int minSign = 1, int maxSign = ushort.MaxValue - 1)
{
var pool = WaitHandlePool;
WaitHandlePool = new WaitHandlePool<MessageBase>(minSign, maxSign);
@@ -47,16 +51,15 @@ public class SerialPortChannel : SerialPortClient, IClientChannel
/// <inheritdoc/>
public DataHandlingAdapter ReadOnlyDataHandlingAdapter => ProtectedDataHandlingAdapter;
private IDeviceDataHandleAdapter _deviceDataHandleAdapter;
private bool logSet;
/// <inheritdoc/>
public void SetDataHandlingAdapterLogger(ILog log)
{
if (_deviceDataHandleAdapter != ProtectedDataHandlingAdapter && ProtectedDataHandlingAdapter is IDeviceDataHandleAdapter handleAdapter)
if (!logSet && ProtectedDataHandlingAdapter is IDeviceDataHandleAdapter handleAdapter)
{
_deviceDataHandleAdapter = handleAdapter;
}
if (_deviceDataHandleAdapter != null)
{
_deviceDataHandleAdapter.Logger = log;
logSet = true;
handleAdapter.Logger = log;
}
}
/// <inheritdoc/>
@@ -64,10 +67,16 @@ public class SerialPortChannel : SerialPortClient, IClientChannel
{
if (adapter is SingleStreamDataHandlingAdapter singleStreamDataHandlingAdapter)
SetAdapter(singleStreamDataHandlingAdapter);
if (adapter is IDeviceDataHandleAdapter deviceDataHandleAdapter)
_deviceDataHandleAdapter = deviceDataHandleAdapter;
logSet = false;
}
public void LogSeted(bool logSeted)
{
logSet = logSeted;
}
/// <inheritdoc/>
public ChannelEventHandler Started { get; } = new();
@@ -82,7 +91,7 @@ public class SerialPortChannel : SerialPortClient, IClientChannel
/// <summary>
/// 等待池
/// </summary>
public WaitHandlePool<MessageBase> WaitHandlePool { get; internal set; } = new(0, ushort.MaxValue);
public WaitHandlePool<MessageBase> WaitHandlePool { get; internal set; } = new(1, ushort.MaxValue - 1);
/// <inheritdoc/>
public WaitLock WaitLock => ChannelOptions.WaitLock;
@@ -192,16 +201,12 @@ public class SerialPortChannel : SerialPortClient, IClientChannel
/// <inheritdoc/>
protected override async Task OnSerialReceived(ReceivedDataEventArgs e)
{
var receivedTask = base.OnSerialReceived(e);
if (!receivedTask.IsCompleted)
await receivedTask.ConfigureAwait(false);
await base.OnSerialReceived(e).ConfigureAwait(false);
if (e.Handled)
return;
var channelReceivedTask = this.OnChannelReceivedEvent(e, ChannelReceived);
if (!channelReceivedTask.IsCompleted)
await channelReceivedTask.ConfigureAwait(false);
await this.OnChannelReceivedEvent(e, ChannelReceived).ConfigureAwait(false);
}
/// <inheritdoc/>

View File

@@ -17,6 +17,10 @@ namespace ThingsGateway.Foundation;
/// </summary>
public class TcpClientChannel : TcpClient, IClientChannel
{
~TcpClientChannel()
{
this.SafeDispose();
}
/// <inheritdoc/>
public TcpClientChannel(IChannelOptions channelOptions)
{
@@ -24,22 +28,19 @@ public class TcpClientChannel : TcpClient, IClientChannel
ResetSign();
}
public override TouchSocketConfig Config => base.Config ?? ChannelOptions.Config;
public void ResetSign(int minSign = 0, int maxSign = ushort.MaxValue)
public void ResetSign(int minSign = 1, int maxSign = ushort.MaxValue - 1)
{
var pool = WaitHandlePool;
WaitHandlePool = new WaitHandlePool<MessageBase>(minSign, maxSign);
pool?.CancelAll();
}
private IDeviceDataHandleAdapter _deviceDataHandleAdapter;
private bool logSet;
public void SetDataHandlingAdapterLogger(ILog log)
{
if (_deviceDataHandleAdapter != DataHandlingAdapter && DataHandlingAdapter is IDeviceDataHandleAdapter handleAdapter)
if (!logSet && DataHandlingAdapter is IDeviceDataHandleAdapter handleAdapter)
{
_deviceDataHandleAdapter = handleAdapter;
}
if (_deviceDataHandleAdapter != null)
{
_deviceDataHandleAdapter.Logger = log;
logSet = true;
handleAdapter.Logger = log;
}
}
/// <inheritdoc/>
@@ -47,8 +48,12 @@ public class TcpClientChannel : TcpClient, IClientChannel
{
if (adapter is SingleStreamDataHandlingAdapter singleStreamDataHandlingAdapter)
SetAdapter(singleStreamDataHandlingAdapter);
if (adapter is IDeviceDataHandleAdapter deviceDataHandleAdapter)
_deviceDataHandleAdapter = deviceDataHandleAdapter;
logSet = false;
}
public void LogSeted(bool logSeted)
{
logSet = logSeted;
}
/// <inheritdoc/>
public ChannelReceivedEventHandler ChannelReceived { get; } = new();
@@ -78,7 +83,7 @@ public class TcpClientChannel : TcpClient, IClientChannel
/// <summary>
/// 等待池
/// </summary>
public WaitHandlePool<MessageBase> WaitHandlePool { get; internal set; } = new(0, ushort.MaxValue);
public WaitHandlePool<MessageBase> WaitHandlePool { get; internal set; } = new(1, ushort.MaxValue - 1);
public virtual WaitLock GetLock(string key) => WaitLock;
/// <inheritdoc/>
@@ -179,16 +184,12 @@ public class TcpClientChannel : TcpClient, IClientChannel
/// <inheritdoc/>
protected override async Task OnTcpReceived(ReceivedDataEventArgs e)
{
var receivedTask = base.OnTcpReceived(e);
if (!receivedTask.IsCompleted)
await receivedTask.ConfigureAwait(false);
await base.OnTcpReceived(e).ConfigureAwait(false);
if (e.Handled)
return;
var channelReceivedTask = this.OnChannelReceivedEvent(e, ChannelReceived);
if (!channelReceivedTask.IsCompleted)
await channelReceivedTask.ConfigureAwait(false);
await this.OnChannelReceivedEvent(e, ChannelReceived).ConfigureAwait(false);
}
/// <inheritdoc/>

View File

@@ -18,6 +18,11 @@ namespace ThingsGateway.Foundation;
/// <typeparam name="TClient"></typeparam>
public abstract class TcpServiceChannelBase<TClient> : TcpService<TClient>, ITcpService<TClient> where TClient : TcpSessionClientChannel, new()
{
~TcpServiceChannelBase()
{
this.SafeDispose();
}
/// <inheritdoc/>
public ConcurrentList<IDevice> Collects { get; } = new();
@@ -205,7 +210,7 @@ public class TcpServiceChannel<TClient> : TcpServiceChannelBase<TClient>, IChann
}
public int MinSign { get; private set; } = 0;
public int MaxSign { get; private set; } = ushort.MaxValue;
public void ResetSign(int minSign = 0, int maxSign = ushort.MaxValue)
public void ResetSign(int minSign = 1, int maxSign = ushort.MaxValue - 1)
{
MinSign = minSign;
MaxSign = maxSign;
@@ -241,16 +246,12 @@ public class TcpServiceChannel<TClient> : TcpServiceChannelBase<TClient>, IChann
/// <inheritdoc/>
protected override async Task OnTcpReceived(TClient socketClient, ReceivedDataEventArgs e)
{
var receivedTask = base.OnTcpReceived(socketClient, e);
if (!receivedTask.IsCompleted)
await receivedTask.ConfigureAwait(false);
await base.OnTcpReceived(socketClient, e).ConfigureAwait(false);
if (e.Handled)
return;
var channelReceivedTask = socketClient.OnChannelReceivedEvent(e, ChannelReceived);
if (!channelReceivedTask.IsCompleted)
await channelReceivedTask.ConfigureAwait(false);
await socketClient.OnChannelReceivedEvent(e, ChannelReceived).ConfigureAwait(false);
}

View File

@@ -17,20 +17,22 @@ namespace ThingsGateway.Foundation;
/// </summary>
public class TcpSessionClientChannel : TcpSessionClient, IClientChannel
{
~TcpSessionClientChannel()
{
this.SafeDispose();
}
/// <inheritdoc/>
public TcpSessionClientChannel()
{
}
private IDeviceDataHandleAdapter _deviceDataHandleAdapter;
private bool logSet;
/// <inheritdoc/>
public void SetDataHandlingAdapterLogger(ILog log)
{
if (_deviceDataHandleAdapter != DataHandlingAdapter && DataHandlingAdapter is IDeviceDataHandleAdapter handleAdapter)
if (!logSet && DataHandlingAdapter is IDeviceDataHandleAdapter handleAdapter)
{
_deviceDataHandleAdapter = handleAdapter;
}
if (_deviceDataHandleAdapter != null)
{
_deviceDataHandleAdapter.Logger = log;
logSet = true;
handleAdapter.Logger = log;
}
}
/// <inheritdoc/>
@@ -38,10 +40,14 @@ public class TcpSessionClientChannel : TcpSessionClient, IClientChannel
{
if (adapter is SingleStreamDataHandlingAdapter singleStreamDataHandlingAdapter)
SetAdapter(singleStreamDataHandlingAdapter);
if (adapter is IDeviceDataHandleAdapter deviceDataHandleAdapter)
_deviceDataHandleAdapter = deviceDataHandleAdapter;
logSet = false;
}
public void ResetSign(int minSign = 0, int maxSign = ushort.MaxValue)
public void LogSeted(bool logSeted)
{
logSet = logSeted;
}
public void ResetSign(int minSign = 1, int maxSign = ushort.MaxValue - 1)
{
var pool = WaitHandlePool;
WaitHandlePool = new WaitHandlePool<MessageBase>(minSign, maxSign);
@@ -76,7 +82,7 @@ public class TcpSessionClientChannel : TcpSessionClient, IClientChannel
/// <summary>
/// 等待池
/// </summary>
public WaitHandlePool<MessageBase> WaitHandlePool { get; private set; } = new(0, ushort.MaxValue);
public WaitHandlePool<MessageBase> WaitHandlePool { get; private set; } = new(1, ushort.MaxValue - 1);
/// <inheritdoc/>
public WaitLock WaitLock { get; internal set; } = new(nameof(TcpSessionClientChannel));
@@ -145,15 +151,11 @@ public class TcpSessionClientChannel : TcpSessionClient, IClientChannel
/// <inheritdoc/>
protected override async Task OnTcpReceived(ReceivedDataEventArgs e)
{
var receivedTask = base.OnTcpReceived(e);
if (!receivedTask.IsCompleted)
await receivedTask.ConfigureAwait(false);
await base.OnTcpReceived(e).ConfigureAwait(false);
if (e.Handled)
return;
var channelReceivedTask = this.OnChannelReceivedEvent(e, ChannelReceived);
if (!channelReceivedTask.IsCompleted)
await channelReceivedTask.ConfigureAwait(false);
await this.OnChannelReceivedEvent(e, ChannelReceived).ConfigureAwait(false);
}
}

View File

@@ -17,6 +17,10 @@ namespace ThingsGateway.Foundation;
/// </summary>
public class UdpSessionChannel : UdpSession, IClientChannel
{
~UdpSessionChannel()
{
this.SafeDispose();
}
private readonly WaitLock _connectLock = new WaitLock(nameof(UdpSessionChannel));
/// <inheritdoc/>
@@ -26,27 +30,31 @@ public class UdpSessionChannel : UdpSession, IClientChannel
ResetSign();
}
public override TouchSocketConfig Config => base.Config ?? ChannelOptions.Config;
private IDeviceDataHandleAdapter _deviceDataHandleAdapter;
private bool logSet;
/// <inheritdoc/>
public void SetDataHandlingAdapterLogger(ILog log)
{
if (_deviceDataHandleAdapter != DataHandlingAdapter && DataHandlingAdapter is IDeviceDataHandleAdapter handleAdapter)
if (!logSet && DataHandlingAdapter is IDeviceDataHandleAdapter handleAdapter)
{
_deviceDataHandleAdapter = handleAdapter;
}
if (_deviceDataHandleAdapter != null)
{
_deviceDataHandleAdapter.Logger = log;
logSet = true;
handleAdapter.Logger = log;
}
}
public void LogSeted(bool logSeted)
{
logSet = logSeted;
}
/// <inheritdoc/>
public void SetDataHandlingAdapter(DataHandlingAdapter adapter)
{
if (adapter is UdpDataHandlingAdapter udpDataHandlingAdapter)
SetAdapter(udpDataHandlingAdapter);
if (adapter is IDeviceDataHandleAdapter deviceDataHandleAdapter)
_deviceDataHandleAdapter = deviceDataHandleAdapter;
logSet = false;
}
public void ResetSign(int minSign = 0, int maxSign = ushort.MaxValue)
public void ResetSign(int minSign = 1, int maxSign = ushort.MaxValue - 1)
{
var pool = WaitHandlePool;
WaitHandlePool = new WaitHandlePool<MessageBase>(minSign, maxSign);
@@ -85,7 +93,7 @@ public class UdpSessionChannel : UdpSession, IClientChannel
/// <summary>
/// 等待池
/// </summary>
public WaitHandlePool<MessageBase> WaitHandlePool { get; set; } = new(0, ushort.MaxValue);
public WaitHandlePool<MessageBase> WaitHandlePool { get; internal set; } = new(1, ushort.MaxValue - 1);
/// <inheritdoc/>
public WaitLock WaitLock => ChannelOptions.WaitLock;
@@ -196,16 +204,12 @@ public class UdpSessionChannel : UdpSession, IClientChannel
/// <inheritdoc/>
protected override async Task OnUdpReceived(UdpReceivedDataEventArgs e)
{
var receivedTask = base.OnUdpReceived(e);
if (!receivedTask.IsCompleted)
await receivedTask.ConfigureAwait(false);
await base.OnUdpReceived(e).ConfigureAwait(false);
if (e.Handled)
return;
var channelReceivedTask = this.OnChannelReceivedEvent(e, ChannelReceived);
if (!channelReceivedTask.IsCompleted)
await channelReceivedTask.ConfigureAwait(false);
await this.OnChannelReceivedEvent(e, ChannelReceived).ConfigureAwait(false);
}

View File

@@ -10,8 +10,6 @@
using System.Text;
using ThingsGateway.NewLife.Collections;
namespace ThingsGateway.Foundation;
/// <summary>
@@ -59,13 +57,6 @@ public class DeviceSingleStreamDataHandleAdapter<TRequest> : CustomDataHandlingA
/// <inheritdoc />
public void SetRequest(ISendMessage sendMessage)
{
if (IsSingleThread)
{
if (Request != null)
{
_requestPool.Return(Request);
}
}
var request = GetInstance();
request.Sign = sendMessage.Sign;
request.SendInfo(sendMessage);
@@ -165,25 +156,13 @@ public class DeviceSingleStreamDataHandleAdapter<TRequest> : CustomDataHandlingA
}
}
private static ObjectPool<TRequest> _requestPool { get; } = new ObjectPool<TRequest>();
/// <summary>
/// 获取泛型实例。
/// </summary>
/// <returns></returns>
protected virtual TRequest GetInstance()
{
if (IsSingleThread)
{
var request = _requestPool.Get();
request.OperCode = -1;
request.Sign = -1;
return request;
}
else
{
return new TRequest() { OperCode = -1, Sign = -1 };
}
return new TRequest() { OperCode = -1, Sign = -1 };
}
public override void SendInput<TWriter>(ref TWriter writer, in ReadOnlyMemory<byte> memory)

View File

@@ -11,8 +11,6 @@
using System.Net;
using System.Text;
using ThingsGateway.NewLife.Collections;
namespace ThingsGateway.Foundation;
/// <summary>
@@ -52,13 +50,6 @@ public class DeviceUdpDataHandleAdapter<TRequest> : UdpDataHandlingAdapter, IDev
/// <inheritdoc />
public void SetRequest(ISendMessage sendMessage)
{
if (IsSingleThread)
{
if (Request != null)
{
_requestPool.Return(Request);
}
}
var request = GetInstance();
request.Sign = sendMessage.Sign;
request.SendInfo(sendMessage);
@@ -71,26 +62,14 @@ public class DeviceUdpDataHandleAdapter<TRequest> : UdpDataHandlingAdapter, IDev
return Owner.ToString();
}
private static ObjectPool<TRequest> _requestPool { get; } = new ObjectPool<TRequest>();
/// <summary>
/// 获取泛型实例。
/// </summary>
/// <returns></returns>
protected virtual TRequest GetInstance()
{
if (IsSingleThread)
{
var request = _requestPool.Get();
request.OperCode = -1;
request.Sign = -1;
return request;
}
else
{
return new TRequest() { OperCode = -1, Sign = -1 };
}
return new TRequest() { OperCode = -1, Sign = -1 };
}

View File

@@ -15,6 +15,7 @@ using System.Net;
using ThingsGateway.Foundation.Extension.Generic;
using ThingsGateway.Foundation.Extension.String;
using ThingsGateway.NewLife;
using ThingsGateway.NewLife.Collections;
using ThingsGateway.NewLife.Extension;
using TouchSocket.SerialPorts;
@@ -330,45 +331,23 @@ public abstract class DeviceBase : AsyncAndSyncDisposableObject, IDevice
}
public bool AutoConnect { get; protected set; } = true;
/// <inheritdoc/>
private async ValueTask<OperResult> SendAsync(ISendMessage sendMessage, IClientChannel channel = default, EndPoint endPoint = default, CancellationToken token = default)
private async Task SendAsync(ISendMessage sendMessage, IClientChannel channel, CancellationToken token = default)
{
try
if (SendDelayTime != 0)
await Task.Delay(SendDelayTime, token).ConfigureAwait(false);
if (channel is IDtuUdpSessionChannel udpSession)
{
if (channel == default)
{
if (Channel is not IClientChannel clientChannel) { throw new ArgumentNullException(nameof(channel)); }
channel = clientChannel;
}
EndPoint? endPoint = GetUdpEndpoint();
await udpSession.SendAsync(endPoint, sendMessage, token).ConfigureAwait(false);
if (SendDelayTime != 0)
await Task.Delay(SendDelayTime, token).ConfigureAwait(false);
if (token.IsCancellationRequested)
return new OperResult(new OperationCanceledException());
if (channel is IDtuUdpSessionChannel udpSession)
{
var sendTask = udpSession.SendAsync(endPoint, sendMessage, token);
if (!sendTask.IsCompleted)
{
await sendTask.ConfigureAwait(false);
}
}
else
{
var sendTask = channel.SendAsync(sendMessage, token);
if (!sendTask.IsCompleted)
{
await sendTask.ConfigureAwait(false);
}
}
return OperResult.Success;
}
catch (Exception ex)
else
{
return new(ex);
await channel.SendAsync(sendMessage, token).ConfigureAwait(false);
}
}
private Task BeforeSendAsync(IClientChannel channel, CancellationToken token)
@@ -415,25 +394,20 @@ public abstract class DeviceBase : AsyncAndSyncDisposableObject, IDevice
{
try
{
var dtuId = this is IDtu dtu1 ? dtu1.DtuId : null;
var channelResult = GetChannel(dtuId);
var channelResult = GetChannel();
if (!channelResult.IsSuccess) return new OperResult<byte[]>(channelResult);
WaitLock? waitLock = GetWaitLock(channelResult.Content, dtuId);
WaitLock? waitLock = GetWaitLock(channelResult.Content);
try
{
var beforeSendTask = BeforeSendAsync(channelResult.Content, cancellationToken);
if (!beforeSendTask.IsCompleted)
{
await beforeSendTask.ConfigureAwait(false);
}
await BeforeSendAsync(channelResult.Content, cancellationToken).ConfigureAwait(false);
await waitLock.WaitAsync(cancellationToken).ConfigureAwait(false);
channelResult.Content.SetDataHandlingAdapterLogger(Logger);
EndPoint? endPoint = GetUdpEndpoint(dtuId);
return await SendAsync(sendMessage, channelResult.Content, endPoint, cancellationToken).ConfigureAwait(false);
await SendAsync(sendMessage, channelResult.Content, cancellationToken).ConfigureAwait(false);
return OperResult.Success;
}
finally
{
@@ -442,15 +416,18 @@ public abstract class DeviceBase : AsyncAndSyncDisposableObject, IDevice
}
catch (Exception ex)
{
if (!cancellationToken.IsCancellationRequested)
await Task.Delay(1000, cancellationToken).ConfigureAwait(false);
return new(ex);
}
}
/// <inheritdoc/>
public virtual OperResult<IClientChannel> GetChannel(string socketId)
public virtual OperResult<IClientChannel> GetChannel()
{
if (Channel is IClientChannel clientChannel1)
return new OperResult<IClientChannel>() { Content = clientChannel1 };
var socketId = this is IDtu dtu1 ? dtu1.DtuId : null;
if (string.IsNullOrWhiteSpace(socketId))
{
if (Channel is IClientChannel clientChannel)
@@ -485,10 +462,11 @@ public abstract class DeviceBase : AsyncAndSyncDisposableObject, IDevice
}
/// <inheritdoc/>
public virtual EndPoint GetUdpEndpoint(string socketId)
public virtual EndPoint GetUdpEndpoint()
{
if (Channel is IDtuUdpSessionChannel udpSessionChannel)
{
var socketId = this is IDtu dtu1 ? dtu1.DtuId : null;
if (string.IsNullOrWhiteSpace(socketId))
return udpSessionChannel.DefaultEndpoint;
@@ -514,7 +492,7 @@ public abstract class DeviceBase : AsyncAndSyncDisposableObject, IDevice
/// <inheritdoc/>
public virtual ValueTask<OperResult<ReadOnlyMemory<byte>>> SendThenReturnAsync(ISendMessage sendMessage, CancellationToken cancellationToken = default)
{
var channelResult = GetChannel(this is IDtu dtu ? dtu.DtuId : null);
var channelResult = GetChannel();
if (!channelResult.IsSuccess) return EasyValueTask.FromResult(new OperResult<ReadOnlyMemory<byte>>(channelResult));
return SendThenReturnAsync(sendMessage, channelResult.Content, cancellationToken);
}
@@ -524,18 +502,8 @@ public abstract class DeviceBase : AsyncAndSyncDisposableObject, IDevice
{
try
{
var sendTask = SendThenReturnMessageAsync(sendMessage, channel, cancellationToken);
if (!sendTask.IsCompleted)
{
var result = await sendTask.ConfigureAwait(false);
return new OperResult<ReadOnlyMemory<byte>>(result) { Content = result.Content };
}
else
{
var result = sendTask.Result;
return new OperResult<ReadOnlyMemory<byte>>(result) { Content = result.Content };
}
var result = await SendThenReturnMessageAsync(sendMessage, channel, cancellationToken).ConfigureAwait(false);
return new OperResult<ReadOnlyMemory<byte>>(result) { Content = result.Content };
}
catch (Exception ex)
{
@@ -546,7 +514,7 @@ public abstract class DeviceBase : AsyncAndSyncDisposableObject, IDevice
/// <inheritdoc/>
protected virtual ValueTask<MessageBase> SendThenReturnMessageAsync(ISendMessage sendMessage, CancellationToken cancellationToken = default)
{
var channelResult = GetChannel(this is IDtu dtu ? dtu.DtuId : null);
var channelResult = GetChannel();
if (!channelResult.IsSuccess) return EasyValueTask.FromResult(new MessageBase(channelResult));
return SendThenReturnMessageAsync(sendMessage, channelResult.Content, cancellationToken);
}
@@ -557,103 +525,93 @@ public abstract class DeviceBase : AsyncAndSyncDisposableObject, IDevice
return GetResponsedDataAsync(command, clientChannel, Timeout, cancellationToken);
}
private ObjectPool<ReusableCancellationTokenSource> _reusableTimeouts = new();
/// <summary>
/// 发送并等待数据
/// </summary>
protected async ValueTask<MessageBase> GetResponsedDataAsync(ISendMessage command, IClientChannel clientChannel, int timeout = 3000, CancellationToken cancellationToken = default)
protected async ValueTask<MessageBase> GetResponsedDataAsync(
ISendMessage command,
IClientChannel clientChannel,
int timeout = 3000,
CancellationToken cancellationToken = default)
{
var waitData = clientChannel.WaitHandlePool.GetWaitDataAsync(out var sign);
command.Sign = sign;
WaitLock? waitLock = null;
try
{
var beforeSendTask = BeforeSendAsync(clientChannel, cancellationToken);
if (!beforeSendTask.IsCompleted)
{
await beforeSendTask.ConfigureAwait(false);
}
var dtuId = this is IDtu dtu1 ? dtu1.DtuId : null;
waitLock = GetWaitLock(clientChannel, dtuId);
await BeforeSendAsync(clientChannel, cancellationToken).ConfigureAwait(false);
waitLock = GetWaitLock(clientChannel);
await waitLock.WaitAsync(cancellationToken).ConfigureAwait(false);
EndPoint? endPoint = GetUdpEndpoint(dtuId);
if (cancellationToken.IsCancellationRequested)
return new MessageBase(new OperationCanceledException());
clientChannel.SetDataHandlingAdapterLogger(Logger);
await SendAsync(command, clientChannel, cancellationToken).ConfigureAwait(false);
if (cancellationToken.IsCancellationRequested)
return new MessageBase(new OperationCanceledException());
if (waitData.Status == WaitDataStatus.Success)
return waitData.CompletedData;
OperResult sendOperResult = default;
var sendTask = SendAsync(command, clientChannel, endPoint, cancellationToken);
if (!sendTask.IsCompleted)
{
sendOperResult = await sendTask.ConfigureAwait(false);
}
else
{
sendOperResult = sendTask.Result;
}
bool timeoutStatus = false;
if (!sendOperResult.IsSuccess)
return new MessageBase(sendOperResult);
using var ctsTime = new CancellationTokenSource(timeout);
var reusableTimeout = _reusableTimeouts.Get();
try
{
using var cts = CancellationTokenSource.CreateLinkedTokenSource(ctsTime.Token, Channel.ClosedToken);
var waitDataTask = waitData.WaitAsync(cts.Token);
if (!waitDataTask.IsCompleted)
{
await waitDataTask.ConfigureAwait(false);
}
var cts = reusableTimeout.GetTokenSource(timeout, cancellationToken, Channel.ClosedToken);
await waitData.WaitAsync(cts.Token).ConfigureAwait(false);
}
catch (OperationCanceledException)
{
if (ctsTime.IsCancellationRequested)
{
return new MessageBase(new TimeoutException());
}
timeoutStatus = reusableTimeout.TimeoutStatus;
return timeoutStatus
? new MessageBase(new TimeoutException()) { ErrorMessage = $"Timeout, sign: {sign}" }
: new MessageBase(new OperationCanceledException());
}
catch (Exception ex)
{
return new MessageBase(ex);
}
finally
{
reusableTimeout.Set();
timeoutStatus = reusableTimeout.TimeoutStatus;
_reusableTimeouts.Return(reusableTimeout);
}
var result = waitData.Check(ctsTime.Token);
if (result.IsSuccess)
if (waitData.Status == WaitDataStatus.Success)
{
return waitData.CompletedData;
}
else
{
return new MessageBase(result);
var operResult = waitData.Check(timeoutStatus);
return new MessageBase(operResult) { ErrorMessage = $"{operResult.ErrorMessage}, sign: {sign}" };
}
}
catch (Exception ex)
{
if (!cancellationToken.IsCancellationRequested)
await Task.Delay(1000, cancellationToken).ConfigureAwait(false);
return new MessageBase(ex);
}
finally
{
waitLock?.Release();
waitData?.SafeDispose();
}
}
private static WaitLock GetWaitLock(IClientChannel clientChannel, string dtuId)
private WaitLock GetWaitLock(IClientChannel clientChannel)
{
WaitLock? waitLock = null;
if (clientChannel is IDtuUdpSessionChannel udpSessionChannel)
{
waitLock = udpSessionChannel.GetLock(dtuId);
waitLock = udpSessionChannel.GetLock(this is IDtu dtu1 ? dtu1.DtuId : null);
}
waitLock ??= clientChannel.GetLock(null);
return waitLock;
@@ -677,7 +635,7 @@ public abstract class DeviceBase : AsyncAndSyncDisposableObject, IDevice
DataTypeEnum.UInt32 => await ReadUInt32Async(address, length, cancellationToken: cancellationToken).ConfigureAwait(false),
DataTypeEnum.Int64 => await ReadInt64Async(address, length, cancellationToken: cancellationToken).ConfigureAwait(false),
DataTypeEnum.UInt64 => await ReadUInt64Async(address, length, cancellationToken: cancellationToken).ConfigureAwait(false),
DataTypeEnum.Single => await ReadSingleAsync(address, length, cancellationToken: cancellationToken).ConfigureAwait(false),
DataTypeEnum.Float => await ReadSingleAsync(address, length, cancellationToken: cancellationToken).ConfigureAwait(false),
DataTypeEnum.Double => await ReadDoubleAsync(address, length, cancellationToken: cancellationToken).ConfigureAwait(false),
DataTypeEnum.Decimal => await ReadDecimalAsync(address, length, cancellationToken: cancellationToken).ConfigureAwait(false),
_ => new OperResult<Array>(string.Format(AppResource.DataTypeNotSupported, dataType)),
@@ -703,7 +661,7 @@ public abstract class DeviceBase : AsyncAndSyncDisposableObject, IDevice
DataTypeEnum.UInt32 => await WriteAsync(address, jArray.ToObject<UInt32[]>().AsMemory(), cancellationToken: cancellationToken).ConfigureAwait(false),
DataTypeEnum.Int64 => await WriteAsync(address, jArray.ToObject<Int64[]>().AsMemory(), cancellationToken: cancellationToken).ConfigureAwait(false),
DataTypeEnum.UInt64 => await WriteAsync(address, jArray.ToObject<UInt64[]>().AsMemory(), cancellationToken: cancellationToken).ConfigureAwait(false),
DataTypeEnum.Single => await WriteAsync(address, jArray.ToObject<Single[]>().AsMemory(), cancellationToken: cancellationToken).ConfigureAwait(false),
DataTypeEnum.Float => await WriteAsync(address, jArray.ToObject<Single[]>().AsMemory(), cancellationToken: cancellationToken).ConfigureAwait(false),
DataTypeEnum.Double => await WriteAsync(address, jArray.ToObject<Double[]>().AsMemory(), cancellationToken: cancellationToken).ConfigureAwait(false),
DataTypeEnum.Decimal => await WriteAsync(address, jArray.ToObject<Decimal[]>().AsMemory(), cancellationToken: cancellationToken).ConfigureAwait(false),
_ => new OperResult(string.Format(AppResource.DataTypeNotSupported, dataType)),
@@ -722,7 +680,7 @@ public abstract class DeviceBase : AsyncAndSyncDisposableObject, IDevice
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.Single => await WriteAsync(address, value.ToObject<Single>(), 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)),
@@ -912,7 +870,7 @@ public abstract class DeviceBase : AsyncAndSyncDisposableObject, IDevice
public virtual ValueTask<OperResult> WriteAsync(string address, float value, IThingsGatewayBitConverter bitConverter = null, CancellationToken cancellationToken = default)
{
bitConverter ??= ThingsGatewayBitConverter.GetTransByAddress(address);
return WriteAsync(address, bitConverter.GetBytes(value), DataTypeEnum.Single, cancellationToken);
return WriteAsync(address, bitConverter.GetBytes(value), DataTypeEnum.Float, cancellationToken);
}
/// <inheritdoc/>
@@ -985,7 +943,7 @@ public abstract class DeviceBase : AsyncAndSyncDisposableObject, IDevice
public virtual ValueTask<OperResult> WriteAsync(string address, ReadOnlyMemory<float> value, IThingsGatewayBitConverter bitConverter = null, CancellationToken cancellationToken = default)
{
bitConverter ??= ThingsGatewayBitConverter.GetTransByAddress(address);
return WriteAsync(address, bitConverter.GetBytes(value.Span), DataTypeEnum.Single, cancellationToken);
return WriteAsync(address, bitConverter.GetBytes(value.Span), DataTypeEnum.Float, cancellationToken);
}
/// <inheritdoc/>
@@ -1026,6 +984,7 @@ public abstract class DeviceBase : AsyncAndSyncDisposableObject, IDevice
{
lock (Channel)
{
Channel.Starting.Remove(ChannelStarting);
Channel.Stoped.Remove(ChannelStoped);
Channel.Started.Remove(ChannelStarted);
@@ -1066,9 +1025,14 @@ public abstract class DeviceBase : AsyncAndSyncDisposableObject, IDevice
}
Channel.Collects.Remove(this);
if (Channel is IClientChannel clientChannel)
{
clientChannel.LogSeted(false);
}
}
}
_reusableTimeouts?.SafeDispose();
_deviceLogger?.TryDispose();
base.Dispose(disposing);
}
@@ -1118,8 +1082,15 @@ public abstract class DeviceBase : AsyncAndSyncDisposableObject, IDevice
}
Channel.Collects.Remove(this);
if (Channel is IClientChannel clientChannel)
{
clientChannel.LogSeted(false);
}
}
_reusableTimeouts?.SafeDispose();
_deviceLogger?.TryDispose();
base.Dispose(disposing);
}

View File

@@ -134,7 +134,7 @@ public static partial class DeviceExtension
/// <summary>
/// 当状态不是<see cref="WaitDataStatus.Success"/>时返回异常。
/// </summary>
public static OperResult Check(this AsyncWaitData<MessageBase> waitDataAsync, CancellationToken cancellationToken)
public static OperResult Check(this AsyncWaitData<MessageBase> waitDataAsync, bool timeout)
{
switch (waitDataAsync.Status)
{
@@ -142,7 +142,7 @@ public static partial class DeviceExtension
return new();
case WaitDataStatus.Canceled:
if (cancellationToken.IsCancellationRequested)
if (timeout)
{
if (waitDataAsync.CompletedData != null)
{

View File

@@ -426,9 +426,8 @@ public interface IDevice : IDisposable, IDisposableObject, IAsyncDisposable
/// <summary>
/// 获取通道
/// </summary>
/// <param name="socketId"></param>
/// <returns></returns>
OperResult<IClientChannel> GetChannel(string socketId);
OperResult<IClientChannel> GetChannel();
/// <summary>
/// 发送会经过适配器可传入socketId如果为空则默认通道必须为<see cref="IClientChannel"/>类型

View File

@@ -45,12 +45,16 @@ public enum DataTypeEnum
/// <inheritdoc/>
UInt64,
/// <inheritdoc/>
Single,
/// <summary>
/// 大部分人并不认识Single但都认识Float
/// </summary>
Float,
/// <inheritdoc/>
Double,
/// <inheritdoc/>
Decimal,
}

View File

@@ -32,7 +32,7 @@ public static class DataTypeExtensions
DataTypeEnum.UInt32 => 4,
DataTypeEnum.Int64 => 8,
DataTypeEnum.UInt64 => 8,
DataTypeEnum.Single => 4,
DataTypeEnum.Float => 4,
DataTypeEnum.Double => 8,
DataTypeEnum.Decimal => 16,
_ => 0,
@@ -57,7 +57,7 @@ public static class DataTypeExtensions
TypeCode.UInt32 => DataTypeEnum.UInt32,
TypeCode.Int64 => DataTypeEnum.Int64,
TypeCode.UInt64 => DataTypeEnum.UInt64,
TypeCode.Single => DataTypeEnum.Single,
TypeCode.Single => DataTypeEnum.Float,
TypeCode.Double => DataTypeEnum.Double,
TypeCode.Decimal => DataTypeEnum.Decimal,
_ => DataTypeEnum.Object,
@@ -82,7 +82,7 @@ public static class DataTypeExtensions
DataTypeEnum.UInt32 => typeof(uint),
DataTypeEnum.Int64 => typeof(long),
DataTypeEnum.UInt64 => typeof(ulong),
DataTypeEnum.Single => typeof(float),
DataTypeEnum.Float => typeof(float),
DataTypeEnum.Double => typeof(double),
DataTypeEnum.Decimal => typeof(decimal),
_ => typeof(object),

View File

@@ -11,8 +11,8 @@
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Localization.Abstractions" Version="$(NET9Version)" />
<PackageReference Include="TouchSocket" Version="4.0.0-beta.13" />
<PackageReference Include="TouchSocket.SerialPorts" Version="4.0.0-beta.13" />
<PackageReference Include="TouchSocket" Version="4.0.0-beta.28" />
<PackageReference Include="TouchSocket.SerialPorts" Version="4.0.0-beta.28" />
</ItemGroup>
<ItemGroup>

View File

@@ -56,7 +56,7 @@ public static class ThingsGatewayBitConverterExtension
case DataTypeEnum.UInt64:
return byteConverter.GetBytes(value.ToObject<UInt64[]>());
case DataTypeEnum.Single:
case DataTypeEnum.Float:
return byteConverter.GetBytes(value.ToObject<Single[]>());
case DataTypeEnum.Double:
@@ -107,7 +107,7 @@ public static class ThingsGatewayBitConverterExtension
case DataTypeEnum.UInt64:
return byteConverter.GetBytes(value.ToObject<UInt64>());
case DataTypeEnum.Single:
case DataTypeEnum.Float:
return byteConverter.GetBytes(value.ToObject<Single>());
case DataTypeEnum.Double:
@@ -333,7 +333,7 @@ public static class ThingsGatewayBitConverterExtension
return true;
}
case DataTypeEnum.Single:
case DataTypeEnum.Float:
if (arrayLength > 1)
{
var newVal = byteConverter.ToSingle(buffer, index, arrayLength);

View File

@@ -8,35 +8,48 @@
// QQ群605534569
//------------------------------------------------------------------------------
namespace ThingsGateway.Gateway.Application;
namespace ThingsGateway.Foundation;
public class LinkedCancellationTokenSourceCache : IDisposable
{
private CancellationTokenSource? _cachedCts;
private CancellationToken _token1;
private CancellationToken _token2;
private CancellationToken _token3;
private readonly object _lock = new();
~LinkedCancellationTokenSourceCache()
{
Dispose();
}
/// <summary>
/// 获取一个 CancellationTokenSource它是由两个 token 链接而成的。
/// 会尝试复用之前缓存的 CTS前提是两个 token 仍然相同且未取消。
/// </summary>
public CancellationTokenSource GetLinkedTokenSource(CancellationToken token1, CancellationToken token2)
public CancellationTokenSource GetLinkedTokenSource(CancellationToken token1, CancellationToken token2, CancellationToken token3 = default)
{
lock (_lock)
{
// 如果缓存的 CTS 已经取消或 Dispose或者 token 不同,重新创建
if (_cachedCts?.IsCancellationRequested != false ||
!_token1.Equals(token1) || !_token2.Equals(token2))
!_token1.Equals(token1) || !_token2.Equals(token2) || !_token3.Equals(token3))
{
#if NET6_0_OR_GREATER
if (_cachedCts?.TryReset() != true)
{
_cachedCts?.Dispose();
_cachedCts = CancellationTokenSource.CreateLinkedTokenSource(token1, token2, token3);
}
#else
_cachedCts?.Dispose();
_cachedCts = CancellationTokenSource.CreateLinkedTokenSource(token1, token2);
_cachedCts = CancellationTokenSource.CreateLinkedTokenSource(token1, token2, token3);
#endif
_token1 = token1;
_token2 = token2;
_token3 = token3;
}
return _cachedCts;
@@ -54,4 +67,3 @@ public class LinkedCancellationTokenSourceCache : IDisposable
}

View File

@@ -0,0 +1,81 @@
//------------------------------------------------------------------------------
// 此代码版权除特别声明或在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
// 感谢您的下载和使用
//------------------------------------------------------------------------------
namespace ThingsGateway.Foundation;
using System;
using System.Threading;
public sealed class ReusableCancellationTokenSource : IDisposable
{
private readonly Timer _timer;
private CancellationTokenSource? _cts;
public ReusableCancellationTokenSource()
{
_timer = new Timer(OnTimeout, null, Timeout.Infinite, Timeout.Infinite);
}
public bool TimeoutStatus = false;
private void OnTimeout(object? state)
{
TimeoutStatus = true;
if (_cts?.IsCancellationRequested == false)
_cts?.Cancel();
}
private readonly LinkedCancellationTokenSourceCache _linkedCtsCache = new();
/// <summary>
/// 获取一个 CTS并启动超时
/// </summary>
public CancellationTokenSource GetTokenSource(long timeout, CancellationToken external1 = default, CancellationToken external2 = default, CancellationToken external3 = default)
{
TimeoutStatus = false;
// 创建新的 CTS
_cts = _linkedCtsCache.GetLinkedTokenSource(external1, external2, external3);
// 启动 Timer
_timer.Change(timeout, Timeout.Infinite);
return _cts;
}
public void Set()
{
_timer?.Change(Timeout.Infinite, Timeout.Infinite);
}
/// <summary>
/// 手动取消
/// </summary>
public void Cancel()
{
_cts?.SafeCancel();
}
public void Dispose()
{
_cts?.SafeCancel();
_cts?.SafeDispose();
_linkedCtsCache.SafeDispose();
_timer.SafeDispose();
}
}

View File

@@ -3,7 +3,7 @@
<Import Project="..\..\PackNuget.props" />
<PropertyGroup>
<TargetFrameworks>net8.0;net9.0</TargetFrameworks>
<TargetFrameworks>net8.0</TargetFrameworks>
</PropertyGroup>

View File

@@ -214,6 +214,17 @@ public class ControlController : ControllerBase, IRpcServer
return GlobalData.VariableRuntimeService.InsertTestDataAsync(testVariableCount, testDeviceCount, slaveUrl, businessEnable, restart);
}
/// <summary>
/// 增加测试Dtu数据
/// </summary>
[HttpPost("insertTestDtuData")]
[DisplayName("增加测试Dtu数据")]
[TouchSocket.WebApi.WebApi(Method = TouchSocket.WebApi.HttpMethodType.Post)]
public Task InsertTestDtuDataAsync(int testDeviceCount, string slaveUrl, bool restart = true)
{
return GlobalData.VariableRuntimeService.InsertTestDtuDataAsync(testDeviceCount, slaveUrl, restart);
}
/// <summary>
/// 确认实时报警
/// </summary>

View File

@@ -338,6 +338,14 @@ public partial class ManagementController : ControllerBase, IRpcServer
public Task InsertTestDataAsync([FromBody] InsertTestDataInput input) =>
App.GetService<IVariablePageService>().InsertTestDataAsync(input.TestVariableCount, input.TestDeviceCount, input.SlaveUrl, input.BusinessEnable, input.Restart);
[HttpPost]
[TouchSocket.WebApi.WebApi(Method = TouchSocket.WebApi.HttpMethodType.Post)]
public Task InsertTestDtuDataAsync([FromBody] InsertTestDtuDataInput input) =>
App.GetService<IVariablePageService>().InsertTestDtuDataAsync(input.TestDeviceCount, input.SlaveUrl, input.Restart);
[HttpPost]
[TouchSocket.WebApi.WebApi(Method = TouchSocket.WebApi.HttpMethodType.Post)]
public Task<bool> IsRedundantDeviceAsync(long id) =>
@@ -628,7 +636,12 @@ public class InsertTestDataInput
public bool BusinessEnable { get; set; }
public bool Restart { get; set; }
}
public class InsertTestDtuDataInput
{
public int TestDeviceCount { get; set; }
public string SlaveUrl { get; set; }
public bool Restart { get; set; }
}
public class LastLogDataInput
{
public string File { get; set; }

View File

@@ -105,6 +105,5 @@ public class BusinessPropertyWithCacheIntervalScript : BusinessPropertyWithCache
/// 报警实体脚本
/// </summary>
[DynamicProperty]
[AutoGenerateColumn(Visible = true, IsVisibleWhenEdit = false, IsVisibleWhenAdd = false)]
public string? BigTextScriptPluginEventDataModel { get; set; }
public virtual string? BigTextScriptPluginEventDataModel { get; set; }
}

View File

@@ -55,7 +55,15 @@ public abstract partial class CollectBase : DriverBase
/// 特殊方法
/// </summary>
public List<DriverMethodInfo>? DriverMethodInfos { get; private set; }
protected virtual bool IsRuntimeSourceValid(VariableRuntime a)
{
//筛选特殊变量地址
//1、DeviceStatus
return !a.RegisterAddress.Equals(nameof(DeviceRuntime.DeviceStatus), StringComparison.OrdinalIgnoreCase) &&
!a.RegisterAddress.Equals("Script", StringComparison.OrdinalIgnoreCase) &&
!a.RegisterAddress.Equals("ScriptRead", StringComparison.OrdinalIgnoreCase)
;
}
public override async Task AfterVariablesChangedAsync(CancellationToken cancellationToken)
{
LogMessage?.LogInformation("Refresh variable");
@@ -84,21 +92,13 @@ public abstract partial class CollectBase : DriverBase
&& string.IsNullOrEmpty(it.OtherMethod)
&& !string.IsNullOrEmpty(it.RegisterAddress));
//筛选特殊变量地址
//1、DeviceStatus
Func<VariableRuntime, bool> source = (a =>
{
return !a.RegisterAddress.Equals(nameof(DeviceRuntime.DeviceStatus), StringComparison.OrdinalIgnoreCase) &&
!a.RegisterAddress.Equals("Script", StringComparison.OrdinalIgnoreCase) &&
!a.RegisterAddress.Equals("ScriptRead", StringComparison.OrdinalIgnoreCase)
;
});
var now = DateTime.Now;
#pragma warning disable CA1851
try
{
currentDevice.VariableScriptReads = tags.Where(a => !source(a)).Select(a =>
currentDevice.VariableScriptReads = tags.Where(a => !IsRuntimeSourceValid(a)).Select(a =>
{
var data = new VariableScriptRead();
data.VariableRuntime = a;
@@ -111,9 +111,9 @@ public abstract partial class CollectBase : DriverBase
// 如果出现异常,记录日志并初始化 VariableSourceReads 属性为新实例
currentDevice.VariableScriptReads = new();
LogMessage?.LogWarning(ex, string.Format(AppResource.VariablePackError, ex.Message));
tags.Where(a => !source(a)).ForEach(a => a.SetValue(null, now, isOnline: false));
tags.Where(a => !IsRuntimeSourceValid(a)).ForEach(a => a.SetValue(null, now, isOnline: false));
}
var variableReads = tags.Where(source).ToList();
var variableReads = tags.Where(IsRuntimeSourceValid).ToList();
try
{
// 将打包后的结果存储在当前设备的 VariableSourceReads 属性中

View File

@@ -0,0 +1,53 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://thingsgateway.cn/
// QQ群605534569
//------------------------------------------------------------------------------
namespace ThingsGateway.Gateway.Application;
/// <summary>
/// 插件配置项
/// <br></br>
/// 使用<see cref="DynamicPropertyAttribute"/> 标识所需的配置属性
/// </summary>
public class MempryDevicePropertyBase : CollectPropertyBase
{
}
/// <summary>
/// <para></para>
/// 采集插件继承实现不同PLC通讯
/// <para></para>
/// </summary>
public class MempryDevice : CollectBase
{
private MempryDevicePropertyBase _driverPropertyBase = new MempryDevicePropertyBase();
public override CollectPropertyBase CollectProperties => _driverPropertyBase;
#if !Management
/// <summary>
/// 是否连接成功
/// </summary>
public override bool IsConnected()
{
return true;
}
protected override Task<List<VariableSourceRead>> ProtectedLoadSourceReadAsync(List<VariableRuntime> deviceVariables)
{
return Task.FromResult(new List<VariableSourceRead>());
}
protected override bool IsRuntimeSourceValid(VariableRuntime a)
{
return false;
}
#endif
}

View File

@@ -409,7 +409,7 @@ internal sealed class DeviceThreadManage : IAsyncDisposable, IDeviceThreadManage
// 查找具有指定设备ID的驱动程序对象
if (Drivers.TryRemove(deviceId, out var driver))
{
driver.CurrentDevice.SetDeviceStatus(now, false, "Communication connection has been removed");
driver.CurrentDevice.SetDeviceStatus(now, true, "Communication connection has been removed");
if (IsCollectChannel == true)
{
foreach (var a in driver.IdVariableRuntimes)

View File

@@ -221,7 +221,8 @@ public interface IManagementRpcServer : IRpcServer
[DmtpRpc]
Task InsertTestDataAsync(int testVariableCount, int testDeviceCount, string slaveUrl, bool businessEnable, bool restart);
[DmtpRpc]
Task InsertTestDtuDataAsync(int testDeviceCount, string slaveUrl, bool restart);
[DmtpRpc]
Task<bool> IsRedundantDeviceAsync(long id);

View File

@@ -179,6 +179,10 @@ public partial class ManagementRpcServer : IRpcServer, IManagementRpcServer, IBa
public Task InsertTestDataAsync(int testVariableCount, int testDeviceCount, string slaveUrl, bool businessEnable, bool restart) =>
App.GetService<IVariablePageService>().InsertTestDataAsync(testVariableCount, testDeviceCount, slaveUrl, businessEnable, restart);
public Task InsertTestDtuDataAsync(int testDeviceCount, string slaveUrl, bool restart) =>
App.GetService<IVariablePageService>().InsertTestDtuDataAsync(testDeviceCount, slaveUrl, restart);
public Task<bool> IsRedundantDeviceAsync(long id) =>
App.GetService<IDevicePageService>().IsRedundantDeviceAsync(id);

View File

@@ -93,7 +93,7 @@ public partial class ManagementTask : AsyncDisposableObject
var config = new TouchSocketConfig()
.SetRemoteIPHost(_managementOptions.ServerUri)
.SetAdapterOption(new AdapterOption() { MaxPackageSize = 1024 * 1024 * 1024 })
.SetDmtpOption(new DmtpOption() { VerifyToken = _managementOptions.VerifyToken })
.SetDmtpOption(a => a.VerifyToken = _managementOptions.VerifyToken)
.ConfigureContainer(a =>
{
a.AddDmtpRouteService();//添加路由策略
@@ -134,7 +134,7 @@ public partial class ManagementTask : AsyncDisposableObject
.SetTick(TimeSpan.FromMilliseconds(_managementOptions.HeartbeatInterval))
.SetMaxFailCount(3);
a.AddDmtpHandshakedPlugin(async () =>
a.AddDmtpCreatedChannelPlugin(async () =>
{
try
{
@@ -157,7 +157,7 @@ public partial class ManagementTask : AsyncDisposableObject
var config = new TouchSocketConfig()
.SetListenIPHosts(_managementOptions.ServerUri)
.SetAdapterOption(new AdapterOption() { MaxPackageSize = 1024 * 1024 * 1024 })
.SetDmtpOption(new DmtpOption() { VerifyToken = _managementOptions.VerifyToken })
.SetDmtpOption(a => a.VerifyToken = _managementOptions.VerifyToken)
.ConfigureContainer(a =>
{
a.AddDmtpRouteService();//添加路由策略

View File

@@ -332,7 +332,7 @@ internal sealed class RedundancyTask : IRpcDriver, IAsyncDisposable
var config = new TouchSocketConfig()
.SetRemoteIPHost(redundancy.MasterUri)
.SetAdapterOption(new AdapterOption() { MaxPackageSize = 0x20000000 })
.SetDmtpOption(new DmtpOption() { VerifyToken = redundancy.VerifyToken })
.SetDmtpOption(a => a.VerifyToken = redundancy.VerifyToken)
.ConfigureContainer(a =>
{
a.AddLogger(LogMessage);
@@ -377,7 +377,7 @@ internal sealed class RedundancyTask : IRpcDriver, IAsyncDisposable
var config = new TouchSocketConfig()
.SetListenIPHosts(redundancy.MasterUri)
.SetAdapterOption(new AdapterOption() { MaxPackageSize = 0x20000000 })
.SetDmtpOption(new DmtpOption() { VerifyToken = redundancy.VerifyToken })
.SetDmtpOption(a => a.VerifyToken = redundancy.VerifyToken)
.ConfigureContainer(a =>
{
a.AddLogger(LogMessage);

View File

@@ -35,5 +35,6 @@ namespace ThingsGateway.Gateway.Application
Task<OperResult<object>> OnWriteVariableAsync(long id, string writeData);
Task<Dictionary<string, ImportPreviewOutputBase>> ImportVariableAsync(IBrowserFile a, bool restart);
Task<Dictionary<string, ImportPreviewOutputBase>> ImportVariableFileAsync(string filePath, bool restart);
Task InsertTestDtuDataAsync(int deviceCount, string slaveUrl, bool restart);
}
}

View File

@@ -111,4 +111,5 @@ internal interface IVariableService
Task<Dictionary<string, ImportPreviewOutputBase>> PreviewAsync(string filePath);
Task<HashSet<long>> ImportVariableAsync(List<Variable> upData, List<Variable> insertData);
Task<Dictionary<string, ImportPreviewOutputBase>> PreviewAsync(IFormFile browserFile);
Task<(List<Channel>, List<Device>, List<Variable>)> InsertTestDtuDataAsync(int deviceCount, string slaveUrl = "127.0.0.1:502");
}

View File

@@ -423,6 +423,45 @@ public class VariableRuntimeService : IVariableRuntimeService
}
}
public async Task InsertTestDtuDataAsync(int deviceCount, string slaveUrl, bool restart)
{
try
{
// await WaitLock.WaitAsync().ConfigureAwait(false);
var datas = await GlobalData.VariableService.InsertTestDtuDataAsync(deviceCount, slaveUrl).ConfigureAwait(false);
{
var newChannelRuntimes = datas.Item1.AdaptListChannelRuntime();
//批量修改之后,需要重新加载通道
RuntimeServiceHelper.Init(newChannelRuntimes);
{
var newDeviceRuntimes = datas.Item2.AdaptListDeviceRuntime();
RuntimeServiceHelper.Init(newDeviceRuntimes);
}
{
var newVariableRuntimes = datas.Item3.AdaptListVariableRuntime();
RuntimeServiceHelper.Init(newVariableRuntimes);
}
//根据条件重启通道线程
if (restart)
{
await GlobalData.ChannelThreadManage.RestartChannelAsync(newChannelRuntimes).ConfigureAwait(false);
await RuntimeServiceHelper.ChangedDriverAsync(_logger).ConfigureAwait(false);
}
}
}
finally
{
//WaitLock.Release();
}
}
public Task<Dictionary<string, ImportPreviewOutputBase>> PreviewAsync(IBrowserFile browserFile)
{
return GlobalData.VariableService.PreviewAsync(browserFile);

View File

@@ -235,6 +235,144 @@ internal sealed class VariableService : BaseService<Variable>, IVariableService
return (newChannels, newDevices, newVariables);
}
public async Task<(List<Channel>, List<Device>, List<Variable>)> InsertTestDtuDataAsync(int deviceCount, string slaveUrl = "127.0.0.1:502")
{
if (slaveUrl.IsNullOrWhiteSpace()) slaveUrl = "127.0.0.1:502";
List<Channel> newChannels = new();
List<Device> newDevices = new();
List<Variable> newVariables = new();
ManageHelper.CheckChannelCount(deviceCount);
ManageHelper.CheckDeviceCount(deviceCount);
ManageHelper.CheckVariableCount(deviceCount);
//DTU
for (int i = 0; i < deviceCount; i++)
{
Channel serviceChannel = new Channel();
Device serviceDevice = new Device();
{
var id = CommonUtils.GetSingleId();
var name = $"modbusSlaveChannel{id}";
serviceChannel.ChannelType = ChannelTypeEnum.TcpClient;
serviceChannel.Name = name;
serviceChannel.Enable = true;
serviceChannel.Id = id;
serviceChannel.CreateUserId = UserManager.UserId;
serviceChannel.CreateOrgId = UserManager.OrgId;
serviceChannel.RemoteUrl = "127.0.0.1:502";
serviceChannel.DtuId = name;
serviceChannel.Heartbeat = "ThingsGateway.Plugin.Modbus";
serviceChannel.PluginName = "ThingsGateway.Plugin.Modbus.ModbusSlave";
newChannels.Add(serviceChannel);
}
{
var id = CommonUtils.GetSingleId();
var name = $"modbusSlaveDevice{id}";
serviceDevice.Name = name;
serviceDevice.Id = id;
serviceDevice.CreateUserId = UserManager.UserId;
serviceDevice.CreateOrgId = UserManager.OrgId;
serviceDevice.ChannelId = serviceChannel.Id;
serviceDevice.IntervalTime = "1000";
newDevices.Add(serviceDevice);
}
}
//SERVICE
var dtuids = newChannels.Select(a => a.Name).ToList();
Channel channel = new Channel();
{
var id = CommonUtils.GetSingleId();
var name = $"modbusChannel{id}";
channel.ChannelType = ChannelTypeEnum.TcpService;
channel.Name = name;
channel.Id = id;
channel.CreateUserId = UserManager.UserId;
channel.CreateOrgId = UserManager.OrgId;
channel.BindUrl = slaveUrl;
channel.Heartbeat = "ThingsGateway.Plugin.Modbus";
channel.PluginName = "ThingsGateway.Plugin.Modbus.ModbusMaster";
//动态插件属性默认
newChannels.Add(channel);
}
foreach (var item in dtuids)
{
Device device = new Device();
{
var id = CommonUtils.GetSingleId();
var name = $"modbusDevice{id}";
device.Name = name;
device.Id = id;
device.ChannelId = channel.Id;
device.CreateUserId = UserManager.UserId;
device.CreateOrgId = UserManager.OrgId;
device.IntervalTime = "1000";
device.DevicePropertys = new Dictionary<string, string>()
{
{
nameof(CollectFoundationDtuPackPropertyBase.DtuId),item
}
};
//动态插件属性默认
newDevices.Add(device);
}
{
var address = $"400001";
var id = CommonUtils.GetSingleId();
var name = $"modbus{address}";
Variable variable = new Variable();
variable.DataType = DataTypeEnum.Int16;
variable.Name = name;
variable.Id = id;
variable.CreateOrgId = UserManager.OrgId;
variable.CreateUserId = UserManager.UserId;
variable.DeviceId = device.Id;
variable.RegisterAddress = address;
newVariables.Add(variable);
}
}
using var db = GetDB();
var result = await db.UseTranAsync(async () =>
{
if (GlobalData.HardwareJob.HardwareInfo.MachineInfo.AvailableMemory < 2 * 1024 * 1024 || WebEnableVariable.WebEnable == false)
{
await db.BulkCopyAsync(newChannels, 10000).ConfigureAwait(false);
await db.BulkCopyAsync(newDevices, 10000).ConfigureAwait(false);
await db.BulkCopyAsync(newVariables, 10000).ConfigureAwait(false);
}
else
{
await db.BulkCopyAsync(newChannels, 200000).ConfigureAwait(false);
await db.BulkCopyAsync(newDevices, 200000).ConfigureAwait(false);
await db.BulkCopyAsync(newVariables, 200000).ConfigureAwait(false);
}
}).ConfigureAwait(false);
if (result.IsSuccess)//如果成功了
{
}
else
{
throw new(result.ErrorMessage, result.ErrorException);
}
return (newChannels, newDevices, newVariables);
}
#endregion
/// <summary>

View File

@@ -11,9 +11,9 @@
<PackageReference Include="Riok.Mapperly" Version="4.2.1" ExcludeAssets="runtime" PrivateAssets="all" />
<PackageReference Include="Rougamo.Fody" Version="5.0.1" />
<PackageReference Include="System.Linq.Async" Version="6.0.3" />
<PackageReference Include="TouchSocket.Dmtp" Version="4.0.0-beta.13" />
<!--<PackageReference Include="TouchSocket.WebApi.Swagger" Version="4.0.0-beta.13" />-->
<PackageReference Include="TouchSocket.WebApi" Version="4.0.0-beta.13" />
<PackageReference Include="TouchSocket.Dmtp" Version="4.0.0-beta.28" />
<!--<PackageReference Include="TouchSocket.WebApi.Swagger" Version="4.0.0-beta.28" />-->
<PackageReference Include="TouchSocket.WebApi" Version="4.0.0-beta.28" />
<PackageReference Include="ThingsGateway.Authentication" Version="$(AuthenticationVersion)" />
<!--<ProjectReference Include="..\..\PluginPro\ThingsGateway.Authentication\ThingsGateway.Authentication.csproj" />-->

View File

@@ -48,7 +48,7 @@
}
/*切换高度*/
.quickactions-list.is-open {
height: 300px;
height: 200px;
}
.quickactions-header {

View File

@@ -243,6 +243,7 @@
"BusinessEnable": "BusinessEnable",
"SlaveUrl": "SlaveUrl",
"Test": "Addition of test variables",
"TestDtu": "Addition of test dtu variables",
"TestDeviceCount": "TestDeviceCount",
"TestVariableCount": "TestVariableCount",
"WriteValue": "WriteValue",

View File

@@ -243,6 +243,7 @@
"BusinessEnable": "添加业务设备",
"SlaveUrl": "服务端Url",
"Test": "一键添加测试变量",
"TestDtu": "一键添加Dtu测试变量",
"TestDeviceCount": "采集设备数量",
"TestVariableCount": "变量数量",
"WriteValue": "写入值",

View File

@@ -76,18 +76,34 @@ public partial class DeviceRuntimeInfo1 : IDisposable
{
return;
}
await DialogService.Show(new DialogOption()
{
IsScrolling = false,
ShowMaximizeButton = true,
Size = Size.ExtraExtraLarge,
Title = DeviceRuntime.Name,
Component = BootstrapDynamicComponent.CreateComponent(driver, new Dictionary<string, object?>()
var renderFragment = BootstrapDynamicComponent.CreateComponent(driver, new Dictionary<string, object?>()
{
{nameof(IDriverUIBase.Driver),DeviceRuntime.Driver},
})
});
}).Render();
if (renderFragment != null)
{
var option = new WinBoxOption()
{
Title = DeviceRuntime.Name,
ContentTemplate = renderFragment,
Max = false,
Width = "80%",
Height = "80%",
Top = "0%",
Left = "10%",
Background = "var(--bb-primary-color)",
Overflow = true
};
await WinBoxService.Show(option);
}
}
[Inject]
[NotNull]
private WinBoxService? WinBoxService { get; set; }
#endif
[Inject]

View File

@@ -48,6 +48,14 @@
@Localizer["Check"]
</Button>
</div>
</div>
<div class="col-12 col-md-12 min-height-500">
<BootstrapLabel Value=@PropertyComponentLocalizer["BigTextScriptPluginEventDataModel"] ShowLabelTooltip="true" />
<CodeEditor IsReadonly=@(!CanWrite) ShowLineNo @bind-Value=@businessProperty.BigTextScriptPluginEventDataModel Language="csharp" Theme="vs-dark" />
</div>
</EditTemplate>

View File

@@ -48,8 +48,22 @@ public partial class GatewayMonitorPage
}
else
{
VariableRuntimes = deviceRuntime.Driver?.IdVariableRuntimes?.Where(a => a.Value != null)
if (deviceRuntime.Driver == null)
{
_ = Task.Run(async () =>
{
await Task.Delay(2000);
VariableRuntimes = deviceRuntime.Driver?.IdVariableRuntimes?.Where(a => a.Value != null)
.Select(a => a.Value) ?? Enumerable.Empty<VariableRuntime>();
await InvokeAsync(StateHasChanged);
});
}
else
{
VariableRuntimes = deviceRuntime.Driver?.IdVariableRuntimes?.Where(a => a.Value != null)
.Select(a => a.Value) ?? Enumerable.Empty<VariableRuntime>();
}
}
ChannelRuntimes = Enumerable.Repeat(deviceRuntime.ChannelRuntime, 1);
DeviceRuntimes = Enumerable.Repeat(deviceRuntime, 1);

View File

@@ -127,7 +127,7 @@
@if (WebsiteOption.Value.Demo || App.HostEnvironment.IsDevelopment())
{
<PopConfirmButton Color=Color.Warning Text="@Localizer["Test"]" IsKeepDisabled=@(!AuthorizeButton(AdminOperConst.Add))
IsAsync OnConfirm=@(InsertTestDataAsync)>
IsAsync OnConfirm=@(InsertTestDataAsync) class="me-1">
<BodyTemplate>
<BootstrapInput @bind-Value=TestVariableCount ShowLabel="true" ShowLabelTooltip="true" />
@@ -136,6 +136,16 @@
</BodyTemplate>
</PopConfirmButton>
<PopConfirmButton Color=Color.Warning Text="@Localizer["TestDtu"]" IsKeepDisabled=@(!AuthorizeButton(AdminOperConst.Add))
IsAsync OnConfirm=@(InsertTestDtuDataAsync)>
<BodyTemplate>
<BootstrapInput @bind-Value=TestDeviceCount ShowLabel="true" ShowLabelTooltip="true" />
</BodyTemplate>
</PopConfirmButton>
}
</TableToolbarTemplate>

View File

@@ -28,15 +28,7 @@ public partial class VariableRuntimeInfo : IDisposable
[Parameter]
public IEnumerable<VariableRuntime>? Items { get; set; } = Enumerable.Empty<VariableRuntime>();
private IEnumerable<VariableRuntime>? _previousItemsRef;
protected override async Task OnParametersSetAsync()
{
if (!ReferenceEquals(_previousItemsRef, Items))
{
_previousItemsRef = Items;
await Refresh(null);
}
}
#endif
private static void BeforeShowEditDialogCallback(ITableEditDialogOption<VariableRuntime> tableEditDialogOption)
@@ -481,6 +473,32 @@ finally
}
}
private async Task InsertTestDtuDataAsync()
{
try
{
try
{
await Task.Run(() => VariablePageService.InsertTestDtuDataAsync(TestDeviceCount, SlaveUrl, AutoRestartThread));
}
finally
{
await InvokeAsync(async () =>
{
await ToastService.Default();
await table.QueryAsync();
StateHasChanged();
});
}
}
catch (Exception ex)
{
await InvokeAsync(async () => await ToastService.Warn(ex));
}
}
[Parameter]
public bool AutoRestartThread { get; set; }

View File

@@ -0,0 +1,244 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://kimdiego2098.github.io/
// QQ群605534569
//------------------------------------------------------------------------------
using BenchmarkConsoleApp;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Diagnosers;
using Longbow.Modbus;
using Longbow.TcpSocket;
using Microsoft.Extensions.DependencyInjection;
using System.Net.Sockets;
using ThingsGateway.Foundation.Modbus;
using TouchSocket.Core;
using TouchSocket.Modbus;
using IModbusMaster = NModbus.IModbusMaster;
using ModbusMaster = ThingsGateway.Foundation.Modbus.ModbusMaster;
namespace ThingsGateway.Foundation;
[MemoryDiagnoser]
public class ModbusBenchmark : IDisposable
{
private readonly List<IModbusClient> _lgbModbusClients = [];
private List<ModbusMaster> thingsgatewaymodbuss = new();
private List<IModbusMaster> nmodbuss = new();
//private List<ModbusTcpNet> modbusTcpNets = new();
private List<ModbusTcpMaster> modbusTcpMasters = new();
public ModbusBenchmark()
{
for (int i = 0; i < Program.ClientCount; i++)
{
var clientConfig = new TouchSocket.Core.TouchSocketConfig();
var clientChannel = clientConfig.GetChannel(new ChannelOptions() { ChannelType = ChannelTypeEnum.TcpClient, RemoteUrl = "127.0.0.1:502", MaxConcurrentCount = 10 });
var thingsgatewaymodbus = new ModbusMaster()
{
//modbus协议格式
ModbusType = ModbusTypeEnum.ModbusTcp,
};
thingsgatewaymodbus.InitChannel(clientChannel);
clientChannel.SetupAsync(clientChannel.Config).GetFalseAwaitResult();
clientChannel.Logger.LogLevel = LogLevel.Warning;
thingsgatewaymodbus.ConnectAsync(CancellationToken.None).GetFalseAwaitResult();
thingsgatewaymodbus.ReadAsync("40001", 100).GetAwaiter().GetResult();
thingsgatewaymodbuss.Add(thingsgatewaymodbus);
}
for (int i = 0; i < Program.ClientCount; i++)
{
var factory = new NModbus.ModbusFactory();
var nmodbus = factory.CreateMaster(new TcpClient("127.0.0.1", 502));
nmodbus.ReadHoldingRegistersAsync(1, 0, 100).GetFalseAwaitResult();
nmodbuss.Add(nmodbus);
}
//for (int i = 0; i < Program.ClientCount; i++)
//{
// ModbusTcpNet modbusTcpNet = new();
// modbusTcpNet.IpAddress = "127.0.0.1";
// modbusTcpNet.Port = 502;
// modbusTcpNet.ConnectServer();
// modbusTcpNet.ReadAsync("0", 100).GetFalseAwaitResult();
// modbusTcpNets.Add(modbusTcpNet);
//}
for (int i = 0; i < Program.ClientCount; i++)
{
var client = new ModbusTcpMaster();
client.SetupAsync(new TouchSocketConfig()
.SetRemoteIPHost("127.0.0.1:502")).GetFalseAwaitResult();
client.ConnectAsync(CancellationToken.None).GetFalseAwaitResult();
client.ReadHoldingRegistersAsync(0, 100).GetFalseAwaitResult();
modbusTcpMasters.Add(client);
}
{
var sc = new ServiceCollection();
sc.AddTcpSocketFactory();
sc.AddModbusFactory();
var provider = sc.BuildServiceProvider();
var factory = provider.GetRequiredService<IModbusFactory>();
for (int i = 0; i < Program.ClientCount; i++)
{
var client = factory.GetOrCreateTcpMaster();
client.ConnectAsync("127.0.0.1", 502).GetAwaiter().GetResult();
client.ReadHoldingRegistersAsync(0x01, 0x00, 10).GetAwaiter().GetResult();
_lgbModbusClients.Add(client);
}
}
}
[Benchmark]
public async Task ThingsGateway()
{
//ModbusAddress addr = new ModbusAddress() { FunctionCode = 3, StartAddress = 0, Length = 100 };
List<Task> tasks = new List<Task>();
foreach (var thingsgatewaymodbus in thingsgatewaymodbuss)
{
for (int i = 0; i < Program.TaskNumberOfItems; i++)
{
tasks.Add(Task.Run(async () =>
{
for (int i = 0; i < Program.NumberOfItems; i++)
{
var result = await thingsgatewaymodbus.ModbusReadAsync(new ModbusAddress() { Station = 1, FunctionCode = 3, StartAddress = 0, Length = 100 }).ConfigureAwait(false);
if (!result.IsSuccess)
{
throw new Exception(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss ffff") + result.ToString());
}
var data = TouchSocketBitConverter.ConvertValues<byte, ushort>(result.Content.Span, EndianType.Little);
}
}));
}
}
await Task.WhenAll(tasks);
}
[Benchmark]
public async Task LongbowModbus()
{
List<Task> tasks = new List<Task>();
foreach (var _lgbModbusClient in _lgbModbusClients)
{
for (int i = 0; i < Program.TaskNumberOfItems; i++)
{
tasks.Add(Task.Run(async () =>
{
for (int i = 0; i < Program.NumberOfItems; i++)
{
using var cts = new CancellationTokenSource(3000);
var task = await _lgbModbusClient.ReadHoldingRegistersAsync(1, 0, 100, cts.Token).ConfigureAwait(false);
}
}));
}
}
await Task.WhenAll(tasks);
}
[Benchmark]
public async Task TouchSocket()
{
List<Task> tasks = new List<Task>();
foreach (var modbusTcpMaster in modbusTcpMasters)
{
for (int i = 0; i < Program.TaskNumberOfItems; i++)
{
tasks.Add(Task.Run(async () =>
{
for (int i = 0; i < Program.NumberOfItems; i++)
{
var result = await modbusTcpMaster.ReadHoldingRegistersAsync(0, 100).ConfigureAwait(false);
var data = TouchSocketBitConverter.ConvertValues<byte, ushort>(result.Data.Span, EndianType.Little);
if (!result.IsSuccess)
{
throw new Exception(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss ffff") + result.ToString());
}
}
}));
}
}
await Task.WhenAll(tasks);
}
[Benchmark]
public async Task NModbus4()
{
List<Task> tasks = new List<Task>();
foreach (var nmodbus in nmodbuss)
{
for (int i = 0; i < Program.TaskNumberOfItems; i++)
{
tasks.Add(Task.Run(async () =>
{
for (int i = 0; i < Program.NumberOfItems; i++)
{
var result = await nmodbus.ReadHoldingRegistersAsync(1, 0, 100).ConfigureAwait(false);
}
}));
}
}
await Task.WhenAll(tasks);
}
//并发失败
//[Benchmark]
//public async Task HslCommunication()
//{
// List<Task> tasks = new List<Task>();
// foreach (var modbusTcpNet in modbusTcpNets)
// {
// for (int i = 0; i < Program.TaskNumberOfItems; i++)
// {
// tasks.Add(Task.Run(async () =>
// {
// for (int i = 0; i < Program.NumberOfItems; i++)
// {
// var result = await modbusTcpNet.ReadAsync("0", 100);
// if (!result.IsSuccess)
// {
// throw new Exception(result.Message);
// }
// }
// }));
// }
// }
// await Task.WhenAll(tasks);
//}
public void Dispose()
{
thingsgatewaymodbuss?.ForEach(a => a.Channel.SafeDispose());
thingsgatewaymodbuss?.ForEach(a => a.SafeDispose());
nmodbuss?.ForEach(a => a.SafeDispose());
//modbusTcpNets?.ForEach(a => a.SafeDispose());
_lgbModbusClients?.ForEach(a => a.DisposeAsync().GetAwaiter().GetResult());
}
}

View File

@@ -0,0 +1,154 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://kimdiego2098.github.io/
// QQ群605534569
//------------------------------------------------------------------------------
using BenchmarkConsoleApp;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Diagnosers;
using HslCommunication.Profinet.Siemens;
using S7.Net;
using ThingsGateway.Foundation.SiemensS7;
using TouchSocket.Core;
namespace ThingsGateway.Foundation;
[MemoryDiagnoser]
public class S7Benchmark : IDisposable
{
private List<SiemensS7Master> siemensS7s = new();
private List<Plc> plcs = new();
private List<SiemensS7Net> siemensS7Nets = new();
public S7Benchmark()
{
{
for (int i = 0; i < Program.ClientCount; i++)
{
var clientConfig = new TouchSocket.Core.TouchSocketConfig();
var clientChannel = clientConfig.GetChannel(new ChannelOptions() { ChannelType = ChannelTypeEnum.TcpClient, RemoteUrl = "127.0.0.1:102" });
var siemensS7 = new SiemensS7Master()
{
//modbus协议格式
SiemensS7Type = SiemensTypeEnum.S1500
};
siemensS7.InitChannel(clientChannel);
clientChannel.SetupAsync(clientChannel.Config).GetFalseAwaitResult();
clientChannel.Logger.LogLevel = LogLevel.Warning;
siemensS7.ConnectAsync(CancellationToken.None).GetFalseAwaitResult();
siemensS7.ReadAsync("M1", 100).GetAwaiter().GetResult();
siemensS7s.Add(siemensS7);
}
for (int i = 0; i < Program.ClientCount; i++)
{
var siemensS7Net = new SiemensS7Net(SiemensPLCS.S1500, "127.0.0.1");
siemensS7Net.ConnectServer();
siemensS7Net.ReadAsync("M0", 100).GetFalseAwaitResult();
siemensS7Nets.Add(siemensS7Net);
}
for (int i = 0; i < Program.ClientCount; i++)
{
var plc = new Plc(CpuType.S7300, "127.0.0.1", 102, 0, 0);
plc.Open();//打开plc连接
plc.ReadAsync(DataType.Memory, 1, 0, VarType.Byte, 100).GetFalseAwaitResult();
plcs.Add(plc);
}
}
}
[Benchmark]
public async Task S7netplus()
{
List<Task> tasks = new List<Task>();
foreach (var plc in plcs)
{
for (int i = 0; i < Program.TaskNumberOfItems; i++)
{
tasks.Add(Task.Run(async () =>
{
for (int i = 0; i < Program.NumberOfItems; i++)
{
var result = await plc.ReadAsync(DataType.Memory, 1, 0, VarType.Byte, 100);
}
}));
}
}
await Task.WhenAll(tasks);
}
//并发失败
//[Benchmark]
//public async Task HslCommunication()
//{
// List<Task> tasks = new List<Task>();
// foreach (var siemensS7Net in siemensS7Nets)
// {
// for (int i = 0; i < Program.TaskNumberOfItems; i++)
// {
// tasks.Add(Task.Run(async () =>
// {
// for (int i = 0; i < Program.NumberOfItems; i++)
// {
// var result = await siemensS7Net.ReadAsync("M0", 100);
// if (!result.IsSuccess)
// {
// throw new Exception(result.Message);
// }
// }
// }));
// }
// }
// await Task.WhenAll(tasks);
//}
[Benchmark]
public async Task ThingsGateway()
{
SiemensS7Address[] siemensS7Address = [SiemensS7Address.ParseFrom("M1", 100)];
List<Task> tasks = new List<Task>();
foreach (var siemensS7 in siemensS7s)
{
for (int i = 0; i < Program.TaskNumberOfItems; i++)
{
tasks.Add(Task.Run(async () =>
{
for (int i = 0; i < Program.NumberOfItems; i++)
{
var result = await siemensS7.S7ReadAsync(siemensS7Address);
if (!result.IsSuccess)
{
throw new Exception(result.ToString());
}
}
}));
}
}
await Task.WhenAll(tasks);
}
public void Dispose()
{
plcs.ForEach(a => a.SafeDispose());
siemensS7Nets.ForEach(a => a.SafeDispose());
siemensS7s.ForEach(a => a.Channel.SafeDispose());
siemensS7s.ForEach(a => a.SafeDispose());
}
}

View File

@@ -0,0 +1,62 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://kimdiego2098.github.io/
// QQ群605534569
//------------------------------------------------------------------------------
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Diagnosers;
using ThingsGateway.NewLife.Collections;
namespace ThingsGateway.Foundation;
[MemoryDiagnoser]
public class TimeoutBenchmark
{
[Benchmark]
public async ValueTask CtsWaitAsync()
{
using var otherCts = new CancellationTokenSource();
for (int i1 = 0; i1 < 10; i1++)
for (int i = 0; i < 10; i++)
{
using var ctsTime = new CancellationTokenSource(TimeSpan.FromMilliseconds(10));
using var cts = CancellationTokenSource.CreateLinkedTokenSource(ctsTime.Token, otherCts.Token);
await Task.Delay(5, cts.Token).ConfigureAwait(false); // 模拟工作
}
}
private ObjectPool<ReusableCancellationTokenSource> _reusableTimeouts;
[Benchmark]
public async ValueTask ReusableTimeoutWaitAsync()
{
_reusableTimeouts ??= new();
using var otherCts = new CancellationTokenSource();
for (int i1 = 0; i1 < 10; i1++)
for (int i = 0; i < 10; i++)
{
var _reusableTimeout = _reusableTimeouts.Get();
try
{
await Task.Delay(5, _reusableTimeout.GetTokenSource(10, otherCts.Token).Token).ConfigureAwait(false); // 模拟工作
}
finally
{
_reusableTimeouts.Return(_reusableTimeout);
}
}
_reusableTimeouts.Dispose();
}
}

View File

@@ -0,0 +1,59 @@
//------------------------------------------------------------------------------
// 此代码版权除特别声明或在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首页http://rrqm_home.gitee.io/touchsocket/
// 交流QQ群234762506
// 感谢您的下载和使用
//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
using BenchmarkDotNet.Configs;
using BenchmarkDotNet.Running;
using ThingsGateway.Foundation;
namespace BenchmarkConsoleApp
{
internal class Program
{
public static int ClientCount = 30;
public static int TaskNumberOfItems = 30;
public static int NumberOfItems = 30;
private static async Task Main(string[] args)
{
Console.WriteLine("开始测试前请先启动ModbusSlave建议使用本项目自带的ThingsGateway.Debug.Photino软件开启S7可以用KEPSERVER的S7模拟服务");
Console.WriteLine($"多客户端({ClientCount}),多线程({TaskNumberOfItems})并发读取({NumberOfItems})测试,共{ClientCount * TaskNumberOfItems * NumberOfItems}次");
await Task.CompletedTask;
//ModbusBenchmark modbusBenchmark = new ModbusBenchmark();
//System.Diagnostics.Stopwatch stopwatch = new();
//stopwatch.Start();
//await modbusBenchmark.ThingsGateway();
//stopwatch.Stop();
//Console.WriteLine($"ThingsGateway耗时{stopwatch.ElapsedMilliseconds}ms");
//stopwatch.Restart();
//await modbusBenchmark.TouchSocket();
//stopwatch.Stop();
//Console.WriteLine($"TouchSocket耗时{stopwatch.ElapsedMilliseconds}ms");
//Console.ReadLine();
// BenchmarkRunner.Run<TimeoutBenchmark>(
//ManualConfig.Create(DefaultConfig.Instance)
//.WithOptions(ConfigOptions.DisableOptimizationsValidator)
//);
BenchmarkRunner.Run<ModbusBenchmark>(
ManualConfig.Create(DefaultConfig.Instance)
.WithOptions(ConfigOptions.DisableOptimizationsValidator)
);
// BenchmarkRunner.Run<S7Benchmark>(
//ManualConfig.Create(DefaultConfig.Instance)
//.WithOptions(ConfigOptions.DisableOptimizationsValidator)
//);
}
}
}

View File

@@ -0,0 +1,59 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
</PropertyGroup>
<PropertyGroup>
<AnalysisModeDesign>None</AnalysisModeDesign>
<AnalysisModeDocumentation>None</AnalysisModeDocumentation>
<AnalysisModeGlobalization>None</AnalysisModeGlobalization>
<AnalysisModeInteroperability>None</AnalysisModeInteroperability>
<AnalysisModeMaintainability>None</AnalysisModeMaintainability>
<AnalysisModeNaming>None</AnalysisModeNaming>
<AnalysisModePerformance>None</AnalysisModePerformance>
<AnalysisModeSingleFile>None</AnalysisModeSingleFile>
<AnalysisModeReliability>None</AnalysisModeReliability>
<AnalysisModeSecurity>None</AnalysisModeSecurity>
<AnalysisModeUsage>None</AnalysisModeUsage>
<AnalysisModeStyle>None</AnalysisModeStyle>
<TargetFrameworks>net8.0;</TargetFrameworks>
<LangVersion>13.0</LangVersion>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<Authors>Diego</Authors>
<Company>Diego</Company>
<Product>Diego</Product>
<Copyright>版权所有 © 2023-present Diego</Copyright>
<RepositoryUrl>https://gitee.com/diego2098/ThingsGateway</RepositoryUrl>
<RepositoryType>Gitee</RepositoryType>
<GenerateResxSourceIncludeDefaultValues>true</GenerateResxSourceIncludeDefaultValues>
</PropertyGroup>
<ItemGroup>
<PackageReference Remove="Roslynator.Analyzers">
</PackageReference>
</ItemGroup>
<ItemGroup>
<PackageReference Include="BenchmarkDotNet" Version="0.15.2" />
<PackageReference Include="HslCommunication" Version="12.5.0" />
<PackageReference Include="Longbow.Modbus" Version="9.0.6" />
<PackageReference Include="NModbus" Version="3.0.81" />
<PackageReference Include="NModbus.Serial" Version="3.0.81" />
<PackageReference Include="S7netplus" Version="0.20.0" />
<!--<PackageReference Include="ThingsGateway.Foundation.Modbus" Version="$(DefaultVersion)" />
<PackageReference Include="ThingsGateway.Foundation.SiemensS7" Version="$(DefaultVersion)" />-->
<PackageReference Include="TouchSocket.Modbus" Version="4.0.0-beta.28" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Plugin\ThingsGateway.Foundation.Modbus\ThingsGateway.Foundation.Modbus.csproj" />
<ProjectReference Include="..\..\Plugin\ThingsGateway.Foundation.SiemensS7\ThingsGateway.Foundation.SiemensS7.csproj" />
</ItemGroup>
</Project>

View File

@@ -186,7 +186,7 @@ public class Dlt645_2007Send : ISendMessage
}
var lenSpan = writerLenAnchor.Rewind(ref byteBlock, out var length);
lenSpan.WriteValue<byte>((byte)(length - 1));//数据域长度
lenSpan.WriteValue<byte>((byte)(length));//数据域长度
int num = 0;
for (int index = 0; index < byteBlock.WrittenCount - SendHeadCodeIndex; ++index)

View File

@@ -52,7 +52,7 @@ public class ModbusRequest
/// <summary>
/// 站号
/// </summary>
public byte Station { get; set; }
public byte Station { get; set; } = 1;
#endregion Request
}

View File

@@ -69,7 +69,6 @@ public class ModbusRtuSend : ISendMessage
else if (wf == 15 || wf == 16)
{
var data = ModbusAddress.MasterWriteDatas.ArrayExpandToLengthEven().Span;
WriterExtension.WriteValue(ref byteBlock, (ushort)(data.Length + 7), EndianType.Big);
WriterExtension.WriteValue(ref byteBlock, (byte)ModbusAddress.Station);
WriterExtension.WriteValue(ref byteBlock, (byte)ModbusAddress.WriteFunctionCode);
WriterExtension.WriteValue(ref byteBlock, (ushort)ModbusAddress.StartAddress, EndianType.Big);

View File

@@ -10,6 +10,7 @@
using System.Buffers;
using System.Collections.Concurrent;
using System.Runtime.InteropServices;
using TouchSocket.Sockets;
@@ -96,7 +97,9 @@ public class ModbusSlave : DeviceBase, IModbusAddress
{
return $"{base.GetAddressDescription()}{Environment.NewLine}{ModbusHelper.GetAddressDescription()}";
}
protected override void SetChannel()
{
}
/// <inheritdoc/>
public override DataHandlingAdapter GetDataAdapter()
{
@@ -184,10 +187,28 @@ public class ModbusSlave : DeviceBase, IModbusAddress
/// <inheritdoc/>
private void Init(ModbusRequest mAddress)
{
//自动扩容
ModbusServer01ByteBlocks.GetOrAdd(mAddress.Station, a =>
{
var bytes = new ByteBlock(ushort.MaxValue * 2);
bytes.SetLength(ushort.MaxValue * 2);
var bytes = new ByteBlock(256,
(c) =>
{
var data = ArrayPool<byte>.Shared.Rent(c);
for (int i = 0; i < data.Length; i++)
{
data[i] = 0;
}
return data;
},
(m) =>
{
if (MemoryMarshal.TryGetArray((ReadOnlyMemory<byte>)m, out var result))
{
ArrayPool<byte>.Shared.Return(result.Array);
}
}
);
bytes.SetLength(256);
for (int i = 0; i < bytes.Length; i++)
{
bytes.WriteByte(0);
@@ -197,8 +218,25 @@ public class ModbusSlave : DeviceBase, IModbusAddress
});
ModbusServer02ByteBlocks.GetOrAdd(mAddress.Station, a =>
{
var bytes = new ByteBlock(ushort.MaxValue * 2);
bytes.SetLength(ushort.MaxValue * 2);
var bytes = new ByteBlock(256,
(c) =>
{
var data = ArrayPool<byte>.Shared.Rent(c);
for (int i = 0; i < data.Length; i++)
{
data[i] = 0;
}
return data;
},
(m) =>
{
if (MemoryMarshal.TryGetArray((ReadOnlyMemory<byte>)m, out var result))
{
ArrayPool<byte>.Shared.Return(result.Array);
}
}
);
bytes.SetLength(256);
for (int i = 0; i < bytes.Length; i++)
{
bytes.WriteByte(0);
@@ -208,8 +246,25 @@ public class ModbusSlave : DeviceBase, IModbusAddress
});
ModbusServer03ByteBlocks.GetOrAdd(mAddress.Station, a =>
{
var bytes = new ByteBlock(ushort.MaxValue * 2);
bytes.SetLength(ushort.MaxValue * 2);
var bytes = new ByteBlock(256,
(c) =>
{
var data = ArrayPool<byte>.Shared.Rent(c);
for (int i = 0; i < data.Length; i++)
{
data[i] = 0;
}
return data;
},
(m) =>
{
if (MemoryMarshal.TryGetArray((ReadOnlyMemory<byte>)m, out var result))
{
ArrayPool<byte>.Shared.Return(result.Array);
}
}
);
bytes.SetLength(256);
for (int i = 0; i < bytes.Length; i++)
{
bytes.WriteByte(0);
@@ -219,8 +274,25 @@ public class ModbusSlave : DeviceBase, IModbusAddress
});
ModbusServer04ByteBlocks.GetOrAdd(mAddress.Station, a =>
{
var bytes = new ByteBlock(ushort.MaxValue * 2);
bytes.SetLength(ushort.MaxValue * 2);
var bytes = new ByteBlock(256,
(c) =>
{
var data = ArrayPool<byte>.Shared.Rent(c);
for (int i = 0; i < data.Length; i++)
{
data[i] = 0;
}
return data;
},
(m) =>
{
if (MemoryMarshal.TryGetArray((ReadOnlyMemory<byte>)m, out var result))
{
ArrayPool<byte>.Shared.Return(result.Array);
}
}
);
bytes.SetLength(256);
for (int i = 0; i < bytes.Length; i++)
{
bytes.WriteByte(0);
@@ -278,16 +350,20 @@ public class ModbusSlave : DeviceBase, IModbusAddress
switch (f)
{
case 1:
return OperResult.CreateSuccessResult(ModbusServer01ByteBlock.Memory.Slice(mAddress.StartAddress, len));
ModbusServer01ByteBlock.Position = mAddress.StartAddress;
return OperResult.CreateSuccessResult((ReadOnlyMemory<byte>)ModbusServer01ByteBlock.GetMemory(len).Slice(0, len));
case 2:
return OperResult.CreateSuccessResult(ModbusServer02ByteBlock.Memory.Slice(mAddress.StartAddress, len));
ModbusServer02ByteBlock.Position = mAddress.StartAddress;
return OperResult.CreateSuccessResult((ReadOnlyMemory<byte>)ModbusServer02ByteBlock.GetMemory(len).Slice(0, len));
case 3:
return OperResult.CreateSuccessResult(ModbusServer03ByteBlock.Memory.Slice(mAddress.StartAddress * RegisterByteLength, len));
ModbusServer03ByteBlock.Position = mAddress.StartAddress * RegisterByteLength;
return OperResult.CreateSuccessResult((ReadOnlyMemory<byte>)ModbusServer03ByteBlock.GetMemory(len).Slice(0, len));
case 4:
return OperResult.CreateSuccessResult(ModbusServer04ByteBlock.Memory.Slice(mAddress.StartAddress * RegisterByteLength, len));
ModbusServer04ByteBlock.Position = mAddress.StartAddress * RegisterByteLength;
return OperResult.CreateSuccessResult((ReadOnlyMemory<byte>)ModbusServer04ByteBlock.GetMemory(len).Slice(0, len));
}
}
}

View File

@@ -12,7 +12,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.4" />
<PackageReference Include="OPCFoundation.NetStandard.Opc.Ua.Client.ComplexTypes" Version="1.5.376.244" />
</ItemGroup>

View File

@@ -139,157 +139,192 @@ public partial class SiemensS7Master : DeviceBase
/// <summary>
/// 此方法并不会智能分组以最大化效率减少传输次数因为返回值是byte[],所以一切都按地址数组的顺序执行,最后合并数组
/// </summary>
public async ValueTask<OperResult<ReadOnlyMemory<byte>>> S7ReadAsync(SiemensS7Address[] sAddresss, CancellationToken cancellationToken = default)
public async ValueTask<OperResult<ReadOnlyMemory<byte>>> S7ReadAsync(
SiemensS7Address[] addresses,
CancellationToken cancellationToken = default)
{
var byteBuffer = new ValueByteBlock(512);
try
{
var byteBlock = new ValueByteBlock(2048);
try
foreach (var address in addresses)
{
foreach (var sAddress in sAddresss)
int readCount = 0;
int totalLength = address.Length == 0 ? 1 : address.Length;
int originalStart = address.AddressStart;
try
{
int num = 0;
var addressLen = sAddress.Length == 0 ? 1 : sAddress.Length;
var start = sAddress.AddressStart;
try
while (readCount < totalLength)
{
while (num < addressLen)
// 每次读取的 PDU 长度,循环直到读取完整
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)
{
//pdu长度重复生成报文直至全部生成
int len = Math.Min(addressLen - num, PduLength);
sAddress.Length = len;
var result = await SendThenReturnAsync(new S7Send([sAddress], true), cancellationToken: cancellationToken).ConfigureAwait(false);
if (!result.IsSuccess) return result;
byteBlock.Write(result.Content.Span);
num += len;
if (sAddress.DataCode == S7Area.TM || sAddress.DataCode == S7Area.CT)
if (addresses.Length == 1)
{
sAddress.AddressStart += len / 2;
}
else
{
sAddress.AddressStart += len * 8;
return result;
}
break;
}
}
finally
{
sAddress.AddressStart = start;
readCount += chunkLength;
// 更新地址起点
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 = byteBlock.ToArray() };
}
catch (Exception ex)
{
return new OperResult<ReadOnlyMemory<byte>>(ex);
}
finally
{
byteBlock.SafeDispose();
}
return new OperResult<ReadOnlyMemory<byte>> { Content = byteBuffer.ToArray() };
}
catch (Exception ex)
{
return new OperResult<ReadOnlyMemory<byte>>(ex);
}
finally
{
byteBuffer.SafeDispose();
}
}
/// <summary>
/// 此方法并不会智能分组以最大化效率减少传输次数因为返回值是byte[],所以一切都按地址数组的顺序执行,最后合并数组
/// </summary>
public async ValueTask<Dictionary<SiemensS7Address, OperResult>> S7WriteAsync(SiemensS7Address[] sAddresss, CancellationToken cancellationToken = default)
public async ValueTask<Dictionary<SiemensS7Address, OperResult>> S7WriteAsync(
SiemensS7Address[] addresses,
CancellationToken cancellationToken = default)
{
var dictOperResult = new Dictionary<SiemensS7Address, OperResult>();
void SetFailOperResult(OperResult operResult)
{
foreach (var item in sAddresss)
foreach (var address in addresses)
{
dictOperResult.TryAdd(item, operResult);
dictOperResult.TryAdd(address, operResult);
}
}
var firstAddress = addresses[0];
// 单位写入(位写入)
if (addresses.Length <= 1 && firstAddress.IsBit)
{
var sAddress = sAddresss[0];
if (sAddresss.Length <= 1 && sAddress.IsBit)
var byteBuffer = new ValueByteBlock(512);
try
{
var writeResult = await SendThenReturnAsync(
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);
itemCount++;
if (i == addresses.Length - 1)
addressChunks.Add(currentChunk);
}
else
{
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 < 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)
{
var byteBlock = new ValueByteBlock(2048);
try
{
var wresult = await SendThenReturnAsync(new S7Send([sAddress], false), cancellationToken: cancellationToken).ConfigureAwait(false);
dictOperResult.TryAdd(sAddress, wresult);
return dictOperResult;
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;
}
finally
{
byteBlock.SafeDispose();
}
}
else
{
//多写
List<List<SiemensS7Address>> siemensS7Addresses = new();
ushort dataLen = 0;
ushort itemLen = 1;
List<SiemensS7Address> addresses = new();
for (int i = 0; i < sAddresss.Length; i++)
{
var item = sAddresss[i];
dataLen = (ushort)(dataLen + item.Data.Length + 4);
ushort telegramLen = (ushort)(itemLen * 12 + 19 + dataLen);
if (telegramLen < PduLength)
{
addresses.Add(item);
itemLen++;
if (i == sAddresss.Length - 1)
siemensS7Addresses.Add(addresses);
}
else
{
siemensS7Addresses.Add(addresses);
addresses = new();
dataLen = 0;
itemLen = 1;
dataLen = (ushort)(dataLen + item.Data.Length + 4);
telegramLen = (ushort)(itemLen * 12 + 19 + dataLen);
if (telegramLen < PduLength)
{
addresses.Add(item);
itemLen++;
if (i == sAddresss.Length - 1)
siemensS7Addresses.Add(addresses);
}
else
{
SetFailOperResult(new OperResult("Write length exceeds limit"));
return dictOperResult;
}
}
}
foreach (var item in siemensS7Addresses)
{
try
{
var result = await SendThenReturnAsync(new S7Send(item.ToArray(), false), cancellationToken: cancellationToken).ConfigureAwait(false);
foreach (var i1 in item)
{
dictOperResult.TryAdd(i1, result);
}
}
catch (Exception ex)
{
SetFailOperResult(new OperResult(ex));
return dictOperResult;
}
}
return dictOperResult;
}
return dictOperResult;
}
}
#region
/// <inheritdoc/>

View File

@@ -28,14 +28,14 @@ public class ModbusTest
[Theory]
[InlineData("400045", true, "00010000002F01032C0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000")]
[InlineData("300045", true, "00010000002F01042C0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000")]
[InlineData("100045", true, "000100000009010206000000000000")]
[InlineData("000045", true, "000100000009010106000000000000")]
[InlineData("400045", false, "0001000000060106002C0001", "1", DataTypeEnum.UInt16)]
[InlineData("000045", false, "0001000000060105002CFF00", "true", DataTypeEnum.Boolean)]
[InlineData("400045;w=16", false, "0001000000090110002C0001020001", "1", DataTypeEnum.UInt16)]
[InlineData("000045;w=15", false, "000100000008010F002C00010101", "true", DataTypeEnum.Boolean)]
[InlineData("400045", true, "00020000002F01032C0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000")]
[InlineData("300045", true, "00020000002F01042C0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000")]
[InlineData("100045", true, "000200000009010206000000000000")]
[InlineData("000045", true, "000200000009010106000000000000")]
[InlineData("400045", false, "0002000000060106002C0001", "1", DataTypeEnum.UInt16)]
[InlineData("000045", false, "0002000000060105002CFF00", "true", DataTypeEnum.Boolean)]
[InlineData("400045;w=16", false, "0002000000090110002C0001020001", "1", DataTypeEnum.UInt16)]
[InlineData("000045;w=15", false, "000200000008010F002C00010101", "true", DataTypeEnum.Boolean)]
public async Task ModbusTcp_ReadWrite_OK(string address, bool read, string data, string writeData = null, DataTypeEnum dataTypeEnum = DataTypeEnum.UInt16)
{
var modbusChannel = new TouchSocketConfig().GetChannel(new ChannelOptions()

View File

@@ -29,8 +29,8 @@ public class SiemensS7Test
[Theory]
[InlineData("M100", true, "03 00 00 1B 02 F0 80 32 03 00 00 00 01 00 02 00 06 00 00 04 01 FF 04 00 10 00 00")]
[InlineData("M100", false, "03 00 00 16 02 F0 80 32 03 00 00 00 01 00 02 00 01 00 00 05 01 FF", "1", DataTypeEnum.UInt16)]
[InlineData("M100", true, "03 00 00 1B 02 F0 80 32 03 00 00 00 02 00 02 00 06 00 00 04 01 FF 04 00 10 00 00")]
[InlineData("M100", false, "03 00 00 16 02 F0 80 32 03 00 00 00 02 00 02 00 01 00 00 05 01 FF", "1", DataTypeEnum.UInt16)]
public async Task SiemensS7_ReadWrite_OK(string address, bool read, string data, string writeData = null, DataTypeEnum dataTypeEnum = DataTypeEnum.UInt16)
{
var siemensS7Channel = new TouchSocketConfig().GetChannel(new ChannelOptions()

View File

@@ -39,7 +39,7 @@ public class RealDBProducerProperty : BusinessPropertyWithCacheInterval
/// <summary>
/// 历史表脚本
/// </summary>
[DynamicProperty(Remark = "必须为间隔上传,才生效")]
[DynamicProperty]
[AutoGenerateColumn(Visible = true, IsVisibleWhenEdit = false, IsVisibleWhenAdd = false)]
public string? BigTextScriptHistoryTable { get; set; }
}

View File

@@ -85,7 +85,6 @@ public partial class QuestDBProducer : BusinessBaseWithCacheIntervalVariable
{
_db.DbMaintenance.CreateDatabase();
//必须为间隔上传
if (!_driverPropertys.BigTextScriptHistoryTable.IsNullOrEmpty())
{
DynamicSQLBase? hisModel = CSharpScriptEngineExtension.Do<DynamicSQLBase>(_driverPropertys.BigTextScriptHistoryTable);

View File

@@ -112,7 +112,6 @@ public partial class SqlDBProducer : BusinessBaseWithCacheIntervalVariable
{
_db.DbMaintenance.CreateDatabase();
//必须为间隔上传
if (!_driverPropertys.BigTextScriptHistoryTable.IsNullOrEmpty())
{
var hisModel = CSharpScriptEngineExtension.Do<DynamicSQLBase>(_driverPropertys.BigTextScriptHistoryTable);

View File

@@ -69,13 +69,13 @@ public class SqlDBProducerProperty : BusinessPropertyWithCacheInterval
/// <summary>
/// 实时表脚本
/// </summary>
[DynamicProperty(Remark = "必须为间隔上传,才生效")]
[DynamicProperty]
[AutoGenerateColumn(Visible = true, IsVisibleWhenEdit = false, IsVisibleWhenAdd = false)]
public string? BigTextScriptRealTable { get; set; }
/// <summary>
/// 历史表脚本
/// </summary>
[DynamicProperty(Remark = "必须为间隔上传,才生效")]
[DynamicProperty]
[AutoGenerateColumn(Visible = true, IsVisibleWhenEdit = false, IsVisibleWhenAdd = false)]
public string? BigTextScriptHistoryTable { get; set; }
}

View File

@@ -102,7 +102,6 @@ public partial class TDengineDBProducer : BusinessBaseWithCacheIntervalVariable
{
_db.DbMaintenance.CreateDatabase();
//必须为间隔上传
if (!_driverPropertys.BigTextScriptHistoryTable.IsNullOrEmpty())
{
var hisModel = CSharpScriptEngineExtension.Do<DynamicSQLBase>(_driverPropertys.BigTextScriptHistoryTable);

View File

@@ -114,7 +114,8 @@ public class ModbusSlave : BusinessBase
try
{
await _plc.ConnectAsync(cancellationToken).ConfigureAwait(false);
if (channel.ChannelType == ChannelTypeEnum.TcpService)
await _plc.ConnectAsync(cancellationToken).ConfigureAwait(false);
}
catch
{

View File

@@ -1,5 +1,6 @@
{
"ThingsGateway.Plugin.Mqtt.MqttClientProperty": {
"BigTextScriptPluginEventDataModel": "BigTextScriptPluginEventDataModel",
"CAFile_BrowserFile": "CAFile",
"ClientCertificateFile_BrowserFile": "ClientCertificateFile",
"ClientKeyFile_BrowserFile": "ClientKeyFile",
@@ -40,6 +41,7 @@
"WebSocketUrl": "WebSocketUrl"
},
"ThingsGateway.Plugin.Mqtt.MqttServerProperty": {
"BigTextScriptPluginEventDataModel": "BigTextScriptPluginEventDataModel",
"AnonymousEnable": "AnonymousEnable",
"DeviceRpcEnable": "DeviceRpcEnable",
"IP": "IP",

View File

@@ -1,5 +1,6 @@
{
"ThingsGateway.Plugin.Mqtt.MqttClientProperty": {
"BigTextScriptPluginEventDataModel": "插件事件上传脚本",
"CAFile_BrowserFile": "CA文件",
"ClientCertificateFile_BrowserFile": "客户端证书",
"ClientKeyFile_BrowserFile": "客户端key文件",
@@ -40,6 +41,7 @@
"WebSocketUrl": "WebSocketUrl"
},
"ThingsGateway.Plugin.Mqtt.MqttServerProperty": {
"BigTextScriptPluginEventDataModel": "插件事件上传脚本",
"AnonymousEnable": "允许匿名登录",
"DeviceRpcEnable": "允许Rpc写入",
"IP": "IP",

View File

@@ -124,4 +124,8 @@ public class MqttClientProperty : BusinessPropertyWithCacheIntervalScript
[DynamicProperty]
[AutoGenerateColumn(Visible = true, IsVisibleWhenEdit = false, IsVisibleWhenAdd = false)]
public string? BigTextScriptRpc { get; set; }
[DynamicProperty]
[AutoGenerateColumn(Visible = true, IsVisibleWhenEdit = false, IsVisibleWhenAdd = false)]
public override string? BigTextScriptPluginEventDataModel { get; set; }
}

View File

@@ -76,4 +76,8 @@ public class MqttServerProperty : BusinessPropertyWithCacheIntervalScript
[DynamicProperty]
[AutoGenerateColumn(Visible = true, IsVisibleWhenEdit = false, IsVisibleWhenAdd = false)]
public string? BigTextScriptRpc { get; set; }
[DynamicProperty]
[AutoGenerateColumn(Visible = true, IsVisibleWhenEdit = false, IsVisibleWhenAdd = false)]
public override string? BigTextScriptPluginEventDataModel { get; set; }
}

View File

@@ -1,7 +1,4 @@
{
"Debug": "true",
"LogPath": "Logs/XLog",
"LogFileMaxBytes": "5", //5mb最大日志文件
"LogFileBackups": "50", //50个日志文件
"LogFileFormat": "{0:yyyy_MM_dd}.log"
"LogFileBackups": "50" //50个日志文件
}

View File

@@ -126,7 +126,7 @@ public partial class MainLayout : IDisposable
if (context is TabItem tabItem)
{
await WinboxRender(tabItem.ChildContent, tabItem.Text);
await _tab.RemoveTab(tabItem);
//await _tab.RemoveTab(tabItem);
}
}
[Inject]

View File

@@ -36,6 +36,7 @@ public class Program
ClaimConst.Scheme = $"{typeof(Program).Assembly.GetName().Name}{SchemeHelper.GetOrCreate()}";
Runtime.CreateConfigOnMissing = true;
#region Logo
Console.Write(Environment.NewLine);
@@ -72,8 +73,17 @@ public class Program
if (Runtime.IsLegacyWindows)
builder.Logging.ClearProviders(); //去除默认的事件日志提供者,某些情况下会日志输出异常,导致程序崩溃
}).ConfigureBuilder(builder =>
{
if (Runtime.IsSystemd)
{
builder.Logging.SetMinimumLevel(Microsoft.Extensions.Logging.LogLevel.Warning);
}
if (!builder.Environment.IsDevelopment())
{
builder.Services.AddResponseCompression(
@@ -108,6 +118,8 @@ public class Program
#endif
ReflectionInoHelper.RemoveAllCache();
InstanceFactory.RemoveCache();
})
@@ -136,6 +148,12 @@ public class Program
}).ConfigureBuilder(builder =>
{
if (Runtime.IsSystemd)
{
builder.ConfigureLogging(a => a.SetMinimumLevel(Microsoft.Extensions.Logging.LogLevel.Warning));
}
// 设置接口超时时间和上传大小-Kestrel
builder.ConfigureKestrel(u =>
{
@@ -144,11 +162,7 @@ public class Program
u.Limits.MaxRequestBodySize = null;
}).UseKestrel().Configure(app =>
{
// ✅ 最小中间件
app.Run(context =>
{
return context.Response.WriteAsync("web is disable");
});
app.Run(context => context.Response.WriteAsync("web is disable"));
});
})
.Configure(app =>

View File

@@ -73,7 +73,6 @@ public class Startup : AppStartup
services.AddRazorPages();
services.AddMvcFilter<RequestAuditFilter>();
services.AddControllers()

View File

@@ -35,7 +35,8 @@
<!--<Import Project="targets\Pro3.targets" Condition=" '$(SolutionName)' == 'ThingsGatewayPro' " />
<Import Project="targets\Pro5.targets" Condition=" '$(SolutionName)' == 'ThingsGatewayPro' " />-->
<!--<Import Project="targets\Pro6.targets" Condition=" '$(SolutionName)' == 'ThingsGatewayPro' AND '$(Configuration)' != 'Debug'" />-->
<Import Project="targets\Pro7.targets" Condition=" '$(SolutionName)' == 'ThingsGatewayPro' AND '$(Configuration)' != 'Debug'" />
<!--nuget包解压复制文件上下文动态加载Pro插件-->
<Import Project="targets\Pro7.targets" Condition=" '$(SolutionName)' != 'ThingsGatewayPro' OR '$(Configuration)' != 'Debug'" />
<!--打包复制-->
<Import Project="targets\PluginPublish.targets" />

View File

@@ -1,5 +1,5 @@
#docker run -d --name tg --restart always -p 127.0.0.1:5000:5000 -e ASPNETCORE_ENVIRONMENT=Demo -v /thingsgateway/Keys:/app/Keys -v /thingsgateway/DB:/app/DB --memory="512m" registry.cn-shenzhen.aliyuncs.com/thingsgateway/thingsgateway:latest
#docker run -d --log-driver json-file --log-opt max-size=50m --log-opt max-file=5 --name tg --restart always -p 127.0.0.1:5000:5000 -p 502:502 -e ASPNETCORE_ENVIRONMENT=Demo -v /thingsgateway/Keys:/app/Keys -v /thingsgateway/DB:/app/DB --memory="512m" registry.cn-shenzhen.aliyuncs.com/thingsgateway/thingsgateway:latest
version: "latest" # Docker Compose 配置版本

View File

@@ -25,6 +25,14 @@ Environment=DOTNET_PRINT_TELEMETRY_MESSAGE=false
[Install]
WantedBy=multi-user.target
# 文件配置
# [Journal]
# SystemMaxUse=200M # 日志最大占用200MB磁盘空间
# SystemMaxFileSize=50M # 单个日志文件最大50MB
# MaxRetentionSec=1month # 日志只保留1个月
# RuntimeMaxUse=100M # 内存中最多缓存100MB日志
# 加载服务配置文件
# systemctl daemon-reload

View File

@@ -113,6 +113,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "debug", "debug", "{053AB5FA
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ThingsGateway.Foundation.Demo", "Foundation\ThingsGateway.Foundation.Demo\ThingsGateway.Foundation.Demo.csproj", "{520DEEAA-1CBD-C0CB-2363-EB190D7DE4EA}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ThingsGateway.Foundation.Benchmark", "Plugin\ThingsGateway.Foundation.Benchmark\ThingsGateway.Foundation.Benchmark.csproj", "{B0957BD6-CF77-36E7-B657-2D0DB85F386F}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -287,6 +289,10 @@ Global
{520DEEAA-1CBD-C0CB-2363-EB190D7DE4EA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{520DEEAA-1CBD-C0CB-2363-EB190D7DE4EA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{520DEEAA-1CBD-C0CB-2363-EB190D7DE4EA}.Release|Any CPU.Build.0 = Release|Any CPU
{B0957BD6-CF77-36E7-B657-2D0DB85F386F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B0957BD6-CF77-36E7-B657-2D0DB85F386F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B0957BD6-CF77-36E7-B657-2D0DB85F386F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B0957BD6-CF77-36E7-B657-2D0DB85F386F}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -334,10 +340,11 @@ Global
{E6EF2033-F02A-CDAD-5A72-EE397A89742E} = {36510D70-161F-4241-B8D0-781E21032816}
{053AB5FA-9742-96EC-76A1-2AEC739860C6} = {36510D70-161F-4241-B8D0-781E21032816}
{520DEEAA-1CBD-C0CB-2363-EB190D7DE4EA} = {2AC600BB-4325-4E0A-93A7-B1F53C8E2CA7}
{B0957BD6-CF77-36E7-B657-2D0DB85F386F} = {1D9CD7A3-9700-A851-0ABD-183347D9CC33}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {199B1B96-4F56-4828-9531-813BA02DB282}
RESX_Rules = {"EnabledRules":[]}
RESX_NeutralResourcesLanguage = zh-Hans
RESX_Rules = {"EnabledRules":[]}
EndGlobalSection
EndGlobal