Compare commits
33 Commits
10.11.29.0
...
10.11.68.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6d7d0e468a | ||
|
|
ff1f632de2 | ||
|
|
fc3d7015ee | ||
|
|
40c5acb522 | ||
|
|
f9cc1cbb05 | ||
|
|
cf6e8b58f0 | ||
|
|
615e3bb24c | ||
|
|
4a7534b210 | ||
|
|
58e099cb93 | ||
|
|
a94a9c953c | ||
|
|
35e1ffa3e9 | ||
|
|
4921642151 | ||
|
|
d71ee29da8 | ||
|
|
901aa2d59f | ||
|
|
764957c014 | ||
|
|
0e3898218b | ||
|
|
61f13cef3c | ||
|
|
0b663d9e01 | ||
|
|
6c95c6209f | ||
|
|
4d223d2622 | ||
|
|
e8d7e91b64 | ||
|
|
8175f541ec | ||
|
|
0adbdb926b | ||
|
|
42adee9980 | ||
|
|
427a7404bc | ||
|
|
3658199e0a | ||
|
|
82eedee50a | ||
|
|
6a18fc3e06 | ||
|
|
c37e314ed6 | ||
|
|
a937a85d90 | ||
|
|
35dd4ae9d3 | ||
|
|
0b829ac85c | ||
|
|
aa247422d2 |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -364,8 +364,5 @@ FodyWeavers.xsd
|
||||
|
||||
/src/*Pro*/
|
||||
/src/*Pro*
|
||||
/src/**/*Pro*
|
||||
/src/*pro*
|
||||
/src/*pro*/
|
||||
/src/ThingsGateway.Server/Configuration/GiteeOAuthSettings.json
|
||||
/src/.idea/
|
||||
|
||||
@@ -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!"
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
</Card>
|
||||
|
||||
</div>
|
||||
<div class="col-12 col-sm-10 h-100">
|
||||
<div class="col-12 col-sm-10 h-100 ps-2">
|
||||
<AdminTable @ref=table TItem="SysUser"
|
||||
AutoGenerateColumns="true"
|
||||
ShowAdvancedSearch=false
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\ThingsGateway.Admin.Application\ThingsGateway.Admin.Application.csproj" />
|
||||
<PackageReference Include="BootstrapBlazor.Chart" Version="9.0.0" />
|
||||
<PackageReference Include="BootstrapBlazor.Chart" Version="9.0.1" />
|
||||
<PackageReference Include="BootstrapBlazor.UniverSheet" Version="9.0.5" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
@@ -26,6 +26,8 @@
|
||||
<link rel="stylesheet" href=@($"_content/BootstrapBlazor/css/motronic.min.css?v={this.GetType().Assembly.GetName().Version}") />
|
||||
<link rel="stylesheet" href=@($"ThingsGateway.AdminServer.styles.css?v={this.GetType().Assembly.GetName().Version}") />
|
||||
<link rel="stylesheet" href=@($"{WebsiteConst.DefaultResourceUrl}css/site.css?v={this.GetType().Assembly.GetName().Version}") />
|
||||
<link rel="stylesheet" href=@($"{WebsiteConst.DefaultResourceUrl}css/devui.css?v={this.GetType().Assembly.GetName().Version}") />
|
||||
|
||||
@* <script src=@($"{WebsiteConst.DefaultResourceUrl}js/theme.js") type="module"></script><!-- 初始主题 --> *@
|
||||
<!-- PWA Manifest -->
|
||||
<link rel="manifest" href="./manifest.json" />
|
||||
@@ -44,6 +46,7 @@
|
||||
<!-- PWA Service Worker -->
|
||||
<script type="text/javascript">'serviceWorker' in navigator && navigator.serviceWorker.register('./service-worker.js')</script>
|
||||
|
||||
<script src="pwa-install.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
||||
34
src/Admin/ThingsGateway.AdminServer/wwwroot/pwa-install.js
Normal file
34
src/Admin/ThingsGateway.AdminServer/wwwroot/pwa-install.js
Normal file
@@ -0,0 +1,34 @@
|
||||
let installPromptTriggered = false;
|
||||
|
||||
function getCookie(name) {
|
||||
const match = document.cookie.match(new RegExp('(^| )' + name + '=([^;]+)'));
|
||||
return match ? match[2] : null;
|
||||
}
|
||||
|
||||
function hasShownInstallPrompt() {
|
||||
return getCookie("tgPWAInstallPromptShown") === "true";
|
||||
}
|
||||
|
||||
function markInstallPromptShown() {
|
||||
document.cookie = "tgPWAInstallPromptShown=true; max-age=31536000; path=/";
|
||||
}
|
||||
|
||||
window.addEventListener('beforeinstallprompt', (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
if (!hasShownInstallPrompt() && !installPromptTriggered) {
|
||||
installPromptTriggered = true;
|
||||
setTimeout(() => {
|
||||
e.prompt()
|
||||
.then(() => e.userChoice)
|
||||
.then(choiceResult => {
|
||||
markInstallPromptShown();
|
||||
})
|
||||
.catch(err => {
|
||||
// 可选错误处理
|
||||
});
|
||||
}, 2000); // 延迟 2 秒提示
|
||||
} else {
|
||||
// console.log("已提示过安装,不再弹出");
|
||||
}
|
||||
});
|
||||
@@ -24,7 +24,7 @@ public static class ParallelExtensions
|
||||
/// <typeparam name="T">集合元素类型</typeparam>
|
||||
/// <param name="source">要操作的集合</param>
|
||||
/// <param name="body">要执行的操作</param>
|
||||
public static void ParallelForEach<T>(this IList<T> source, Action<T> body)
|
||||
public static void ParallelForEach<T>(this IEnumerable<T> source, Action<T> body)
|
||||
{
|
||||
ParallelOptions options = new();
|
||||
options.MaxDegreeOfParallelism = Environment.ProcessorCount;
|
||||
@@ -38,7 +38,7 @@ public static class ParallelExtensions
|
||||
/// <typeparam name="T">集合元素类型</typeparam>
|
||||
/// <param name="source">要操作的集合</param>
|
||||
/// <param name="body">要执行的操作</param>
|
||||
public static void ParallelForEach<T>(this IList<T> source, Action<T, ParallelLoopState, long> body)
|
||||
public static void ParallelForEach<T>(this IEnumerable<T> source, Action<T, ParallelLoopState, long> body)
|
||||
{
|
||||
ParallelOptions options = new();
|
||||
options.MaxDegreeOfParallelism = Environment.ProcessorCount;
|
||||
@@ -53,7 +53,7 @@ public static class ParallelExtensions
|
||||
/// <param name="source">要操作的集合</param>
|
||||
/// <param name="body">要执行的操作</param>
|
||||
/// <param name="parallelCount">最大并行度</param>
|
||||
public static void ParallelForEach<T>(this IList<T> source, Action<T> body, int parallelCount)
|
||||
public static void ParallelForEach<T>(this IEnumerable<T> source, Action<T> body, int parallelCount)
|
||||
{
|
||||
// 创建并行操作的选项对象,设置最大并行度为指定的值
|
||||
var options = new ParallelOptions();
|
||||
@@ -109,7 +109,7 @@ public static class ParallelExtensions
|
||||
/// <param name="parallelCount">最大并行度</param>
|
||||
/// <param name="cancellationToken">取消操作的标志</param>
|
||||
/// <returns>表示异步操作的任务</returns>
|
||||
public static Task ParallelForEachAsync<T>(this IList<T> source, Func<T, CancellationToken, ValueTask> body, int parallelCount, CancellationToken cancellationToken = default)
|
||||
public static Task ParallelForEachAsync<T>(this IEnumerable<T> source, Func<T, CancellationToken, ValueTask> body, int parallelCount, CancellationToken cancellationToken = default)
|
||||
{
|
||||
// 创建并行操作的选项对象,设置最大并行度和取消标志
|
||||
var options = new ParallelOptions();
|
||||
@@ -126,7 +126,7 @@ public static class ParallelExtensions
|
||||
/// <param name="body">异步执行的操作</param>
|
||||
/// <param name="cancellationToken">取消操作的标志</param>
|
||||
/// <returns>表示异步操作的任务</returns>
|
||||
public static Task ParallelForEachAsync<T>(this IList<T> source, Func<T, CancellationToken, ValueTask> body, CancellationToken cancellationToken = default)
|
||||
public static Task ParallelForEachAsync<T>(this IEnumerable<T> source, Func<T, CancellationToken, ValueTask> body, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return ParallelForEachAsync(source, body, Environment.ProcessorCount, cancellationToken);
|
||||
}
|
||||
|
||||
@@ -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.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -27,10 +27,15 @@ public abstract class PrimaryIdEntity : IPrimaryIdEntity
|
||||
public virtual long Id { get; set; }
|
||||
}
|
||||
|
||||
public interface IPrimaryKeyEntity
|
||||
{
|
||||
string ExtJson { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 主键实体基类
|
||||
/// </summary>
|
||||
public abstract class PrimaryKeyEntity : PrimaryIdEntity
|
||||
public abstract class PrimaryKeyEntity : PrimaryIdEntity, IPrimaryKeyEntity
|
||||
{
|
||||
/// <summary>
|
||||
/// 拓展信息
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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} 释放失败,当前信号量无需释放"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace ThingsGateway.Foundation;
|
||||
namespace ThingsGateway.NewLife.Json.Extension;
|
||||
|
||||
/// <summary>
|
||||
/// JTokenUtil
|
||||
@@ -131,6 +131,63 @@ public static class JTokenUtil
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 把任意对象转换为 JToken。
|
||||
/// 支持 JsonElement、JToken、本地 CLR 类型。
|
||||
/// </summary>
|
||||
public static JToken GetJTokenFromObj(this object value)
|
||||
{
|
||||
if (value == null)
|
||||
return JValue.CreateNull();
|
||||
|
||||
switch (value)
|
||||
{
|
||||
case JToken jt:
|
||||
return jt;
|
||||
#if NET6_0_OR_GREATER
|
||||
case System.Text.Json.JsonElement elem:
|
||||
return elem.ToJToken();
|
||||
#endif
|
||||
case string s:
|
||||
return new JValue(s);
|
||||
|
||||
case bool b:
|
||||
return new JValue(b);
|
||||
|
||||
case int i:
|
||||
return new JValue(i);
|
||||
|
||||
case long l:
|
||||
return new JValue(l);
|
||||
|
||||
case double d:
|
||||
return new JValue(d);
|
||||
|
||||
case float f:
|
||||
return new JValue(f);
|
||||
|
||||
case decimal m:
|
||||
return new JValue(m);
|
||||
|
||||
case DateTime dt:
|
||||
return new JValue(dt);
|
||||
|
||||
case DateTimeOffset dto:
|
||||
return new JValue(dto);
|
||||
|
||||
case Guid g:
|
||||
return new JValue(g);
|
||||
|
||||
default:
|
||||
// 兜底:用 Newtonsoft 来包装成 JToken
|
||||
return JToken.FromObject(value);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
#region json
|
||||
|
||||
/// <summary>
|
||||
@@ -0,0 +1,176 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
#if NET6_0_OR_GREATER
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Numerics;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace ThingsGateway.NewLife.Json.Extension;
|
||||
|
||||
public static class JsonElementExtensions
|
||||
{
|
||||
public static string GetValue(object src, bool parseBoolNumber = false)
|
||||
{
|
||||
if (src == null)
|
||||
return string.Empty;
|
||||
|
||||
switch (src)
|
||||
{
|
||||
case string strValue:
|
||||
return strValue;
|
||||
|
||||
case bool boolValue:
|
||||
return boolValue ? parseBoolNumber ? "1" : "True" : parseBoolNumber ? "0" : "False";
|
||||
|
||||
case JsonElement elem: // System.Text.Json.JsonElement
|
||||
return elem.ValueKind switch
|
||||
{
|
||||
JsonValueKind.String => elem.GetString(),
|
||||
JsonValueKind.Number => elem.GetRawText(), // 或 elem.GetDecimal().ToString()
|
||||
JsonValueKind.True => "1",
|
||||
JsonValueKind.False => "0",
|
||||
JsonValueKind.Null => string.Empty,
|
||||
_ => elem.GetRawText(), // 对象、数组等直接输出 JSON
|
||||
};
|
||||
|
||||
default:
|
||||
return (src).GetJTokenFromObj().ToString();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 将 System.Text.Json.JsonElement 递归转换为 Newtonsoft.Json.Linq.JToken
|
||||
/// - tryParseDates: 是否尝试把字符串解析为 DateTime/DateTimeOffset
|
||||
/// - tryParseGuids: 是否尝试把字符串解析为 Guid
|
||||
/// </summary>
|
||||
public static JToken ToJToken(this JsonElement element, bool tryParseDates = true, bool tryParseGuids = true)
|
||||
{
|
||||
switch (element.ValueKind)
|
||||
{
|
||||
case JsonValueKind.Object:
|
||||
var obj = new JObject();
|
||||
foreach (var prop in element.EnumerateObject())
|
||||
obj.Add(prop.Name, prop.Value.ToJToken(tryParseDates, tryParseGuids));
|
||||
return obj;
|
||||
|
||||
case JsonValueKind.Array:
|
||||
var arr = new JArray();
|
||||
foreach (var item in element.EnumerateArray())
|
||||
arr.Add(item.ToJToken(tryParseDates, tryParseGuids));
|
||||
return arr;
|
||||
|
||||
case JsonValueKind.String:
|
||||
// 优先按语义尝试解析 Guid / DateTimeOffset / DateTime
|
||||
if (tryParseGuids && element.TryGetGuid(out Guid g))
|
||||
return new JValue(g);
|
||||
|
||||
if (tryParseDates && element.TryGetDateTimeOffset(out DateTimeOffset dto))
|
||||
return new JValue(dto);
|
||||
|
||||
if (tryParseDates && element.TryGetDateTime(out DateTime dt))
|
||||
return new JValue(dt);
|
||||
|
||||
return new JValue(element.GetString());
|
||||
|
||||
case JsonValueKind.Number:
|
||||
return NumberElementToJToken(element);
|
||||
|
||||
case JsonValueKind.True:
|
||||
return new JValue(true);
|
||||
|
||||
case JsonValueKind.False:
|
||||
return new JValue(false);
|
||||
|
||||
case JsonValueKind.Null:
|
||||
case JsonValueKind.Undefined:
|
||||
default:
|
||||
return JValue.CreateNull();
|
||||
}
|
||||
}
|
||||
|
||||
private static JToken NumberElementToJToken(JsonElement element)
|
||||
{
|
||||
// 取原始文本(保持原始表示,方便处理超出标准类型范围的数字)
|
||||
string raw = element.GetRawText(); // 例如 "123", "1.23e4"
|
||||
|
||||
// 如果不含小数点或指数,优先尝试整数解析(long / ulong / BigInteger)
|
||||
if (!raw.Contains('.') && !raw.Contains('e') && !raw.Contains('E'))
|
||||
{
|
||||
if (long.TryParse(raw, NumberStyles.AllowLeadingSign, CultureInfo.InvariantCulture, out var l))
|
||||
return new JValue(l);
|
||||
|
||||
if (ulong.TryParse(raw, NumberStyles.None, CultureInfo.InvariantCulture, out var ul))
|
||||
return new JValue(ul);
|
||||
|
||||
if (BigInteger.TryParse(raw, NumberStyles.AllowLeadingSign, CultureInfo.InvariantCulture, out var bi))
|
||||
// BigInteger 可能不被 JValue 直接识别为数字类型,使用 FromObject 保证正确表示
|
||||
return JToken.FromObject(bi);
|
||||
}
|
||||
|
||||
// 含小数或指数,或整数解析失败,尝试 decimal -> double
|
||||
if (decimal.TryParse(raw, NumberStyles.Float, CultureInfo.InvariantCulture, out var dec))
|
||||
return new JValue(dec);
|
||||
|
||||
if (double.TryParse(raw, NumberStyles.Float, CultureInfo.InvariantCulture, out var d))
|
||||
return new JValue(d);
|
||||
|
||||
// 最后兜底:把原始文本当字符串返回(极端情况)
|
||||
return new JValue(raw);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 把 JToken 转成“平面”字符串,适合用于日志或写入 CSV 的单元格:
|
||||
/// - string -> 原文
|
||||
/// - bool -> "1"/"0"
|
||||
/// - number -> 原始数字文本
|
||||
/// - date -> ISO 8601 (o)
|
||||
/// - object/array -> 紧凑的 JSON 文本
|
||||
/// - null/undefined -> empty string
|
||||
/// </summary>
|
||||
public static string JTokenToPlainString(this JToken token)
|
||||
{
|
||||
if (token == null || token.Type == JTokenType.Null || token.Type == JTokenType.Undefined)
|
||||
return string.Empty;
|
||||
|
||||
switch (token.Type)
|
||||
{
|
||||
case JTokenType.String:
|
||||
return token.Value<string>() ?? string.Empty;
|
||||
|
||||
case JTokenType.Boolean:
|
||||
return token.Value<bool>() ? "1" : "0";
|
||||
|
||||
case JTokenType.Integer:
|
||||
case JTokenType.Float:
|
||||
// 保持紧凑数字文本(不加引号)
|
||||
return token.ToString(Formatting.None);
|
||||
|
||||
case JTokenType.Date:
|
||||
{
|
||||
// Date 类型可能是 DateTime 或 DateTimeOffset
|
||||
var val = token.Value<object>();
|
||||
if (val is DateTimeOffset dto) return dto.ToString("o");
|
||||
if (val is DateTime dt) return dt.ToString("o");
|
||||
return token.ToString(Formatting.None);
|
||||
}
|
||||
|
||||
default:
|
||||
// 对象/数组等,返回紧凑 JSON 表示
|
||||
return token.ToString(Formatting.None);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>日志文件上限。超过上限后拆分新日志文件,默认5MB,0表示不限制大小</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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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!"
|
||||
|
||||
@@ -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; }
|
||||
|
||||
@@ -2,5 +2,5 @@
|
||||
|
||||
@if (show)
|
||||
{
|
||||
<Spinner class="ms-auto"></Spinner>
|
||||
<Spinner Size="Size" class="ms-auto"></Spinner>
|
||||
}
|
||||
|
||||
@@ -18,4 +18,7 @@ public partial class SpinnerComponent
|
||||
StateHasChanged();
|
||||
}
|
||||
private bool show;
|
||||
|
||||
[Parameter]
|
||||
public Size Size { get; set; } = Size.Small;
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
<PackageReference Include="CsvHelper" Version="33.1.0" />
|
||||
<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" />
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
<Project>
|
||||
|
||||
<PropertyGroup>
|
||||
<PluginVersion>10.11.29</PluginVersion>
|
||||
<ProPluginVersion>10.11.29</ProPluginVersion>
|
||||
<DefaultVersion>10.11.29</DefaultVersion>
|
||||
<AuthenticationVersion>10.11.3</AuthenticationVersion>
|
||||
<SourceGeneratorVersion>10.11.3</SourceGeneratorVersion>
|
||||
<NET8Version>8.0.19</NET8Version>
|
||||
<NET9Version>9.0.8</NET9Version>
|
||||
<PluginVersion>10.11.68</PluginVersion>
|
||||
<ProPluginVersion>10.11.68</ProPluginVersion>
|
||||
<DefaultVersion>10.11.68</DefaultVersion>
|
||||
<AuthenticationVersion>10.11.6</AuthenticationVersion>
|
||||
<SourceGeneratorVersion>10.11.6</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>
|
||||
|
||||
@@ -136,6 +136,9 @@ public static class CSharpScriptEngineExtension
|
||||
}
|
||||
catch (NullReferenceException)
|
||||
{
|
||||
//如果编译失败,应该不重复编译,避免oom
|
||||
Instance.Set<T>(field, null, TimeSpan.FromHours(1));
|
||||
|
||||
string exString = string.Format(CSScriptResource.CSScriptResource.Error1, typeof(T).FullName);
|
||||
throw new(exString);
|
||||
}
|
||||
|
||||
@@ -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 })
|
||||
|
||||
@@ -14,6 +14,7 @@ using Newtonsoft.Json.Linq;
|
||||
using System.Linq.Expressions;
|
||||
|
||||
using ThingsGateway.Gateway.Application.Extensions;
|
||||
using ThingsGateway.NewLife.Json.Extension;
|
||||
using ThingsGateway.NewLife.Reflection;
|
||||
|
||||
namespace ThingsGateway.Foundation;
|
||||
@@ -68,12 +69,12 @@ public abstract class VariableObject
|
||||
/// <returns></returns>
|
||||
public virtual JToken GetExpressionsValue(object value, VariableRuntimeProperty variableRuntimeProperty)
|
||||
{
|
||||
var jToken = JToken.FromObject(value);
|
||||
var jToken = value.GetJTokenFromObj();
|
||||
if (!string.IsNullOrEmpty(variableRuntimeProperty.Attribute.WriteExpressions))
|
||||
{
|
||||
object rawdata = jToken.GetObjectFromJToken();
|
||||
object data = variableRuntimeProperty.Attribute.WriteExpressions.GetExpressionsResult(rawdata, Device?.Logger);
|
||||
jToken = JToken.FromObject(data);
|
||||
jToken = data.GetJTokenFromObj();
|
||||
}
|
||||
|
||||
return jToken;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
/// 设置数据处理适配器
|
||||
|
||||
@@ -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>
|
||||
/// 设置数据处理适配器。
|
||||
|
||||
@@ -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/>
|
||||
|
||||
@@ -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/>
|
||||
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 };
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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"/>类型
|
||||
|
||||
@@ -45,12 +45,16 @@ public enum DataTypeEnum
|
||||
/// <inheritdoc/>
|
||||
UInt64,
|
||||
|
||||
/// <inheritdoc/>
|
||||
Single,
|
||||
/// <summary>
|
||||
/// 大部分人并不认识Single,但都认识Float
|
||||
/// </summary>
|
||||
Float,
|
||||
|
||||
/// <inheritdoc/>
|
||||
Double,
|
||||
|
||||
/// <inheritdoc/>
|
||||
Decimal,
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -12,6 +12,7 @@ using System.Globalization;
|
||||
using System.Net;
|
||||
|
||||
using ThingsGateway.NewLife.Extension;
|
||||
using ThingsGateway.NewLife.Json.Extension;
|
||||
|
||||
namespace ThingsGateway.Foundation.Extension.String;
|
||||
|
||||
|
||||
@@ -11,8 +11,8 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.Localization.Abstractions" Version="$(NET9Version)" />
|
||||
<PackageReference Include="TouchSocket" Version="4.0.0-beta.25" />
|
||||
<PackageReference Include="TouchSocket.SerialPorts" Version="4.0.0-beta.25" />
|
||||
<PackageReference Include="TouchSocket" Version="4.0.0-beta.57" />
|
||||
<PackageReference Include="TouchSocket.SerialPorts" Version="4.0.0-beta.57" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -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);
|
||||
@@ -435,5 +435,311 @@ public static class ThingsGatewayBitConverterExtension
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 根据数据类型获取实际值
|
||||
/// </summary>
|
||||
public static bool GetChangedDataFormJToken(
|
||||
JToken jToken,
|
||||
DataTypeEnum dataType,
|
||||
int arrayLength,
|
||||
object? oldValue,
|
||||
out object? result)
|
||||
{
|
||||
switch (dataType)
|
||||
{
|
||||
case DataTypeEnum.Boolean:
|
||||
if (arrayLength > 1)
|
||||
{
|
||||
var newVal = jToken.ToObject<Boolean[]>();
|
||||
if (oldValue is bool[] oldArr && newVal.SequenceEqual(oldArr))
|
||||
{
|
||||
result = oldValue;
|
||||
return false;
|
||||
}
|
||||
result = newVal;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
var newVal = jToken.ToObject<Boolean>();
|
||||
if (oldValue is bool oldVal && oldVal == newVal)
|
||||
{
|
||||
result = oldValue;
|
||||
return false;
|
||||
}
|
||||
result = newVal;
|
||||
return true;
|
||||
}
|
||||
|
||||
case DataTypeEnum.Byte:
|
||||
if (arrayLength > 1)
|
||||
{
|
||||
var newVal = jToken.ToObject<Byte[]>();
|
||||
if (oldValue is byte[] oldArr && newVal.SequenceEqual(oldArr))
|
||||
{
|
||||
result = oldValue;
|
||||
return false;
|
||||
}
|
||||
result = newVal;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
var newVal = jToken.ToObject<Byte>();
|
||||
if (oldValue is byte oldVal && oldVal == newVal)
|
||||
{
|
||||
result = oldValue;
|
||||
return false;
|
||||
}
|
||||
result = newVal;
|
||||
return true;
|
||||
}
|
||||
|
||||
case DataTypeEnum.Int16:
|
||||
if (arrayLength > 1)
|
||||
{
|
||||
var newVal = jToken.ToObject<Int16[]>();
|
||||
if (oldValue is short[] oldArr && newVal.SequenceEqual(oldArr))
|
||||
{
|
||||
result = oldValue;
|
||||
return false;
|
||||
}
|
||||
result = newVal;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
var newVal = jToken.ToObject<Int16>();
|
||||
if (oldValue is short oldVal && oldVal == newVal)
|
||||
{
|
||||
result = oldValue;
|
||||
return false;
|
||||
}
|
||||
result = newVal;
|
||||
return true;
|
||||
}
|
||||
|
||||
case DataTypeEnum.UInt16:
|
||||
if (arrayLength > 1)
|
||||
{
|
||||
var newVal = jToken.ToObject<UInt16[]>();
|
||||
if (oldValue is ushort[] oldArr && newVal.SequenceEqual(oldArr))
|
||||
{
|
||||
result = oldValue;
|
||||
return false;
|
||||
}
|
||||
result = newVal;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
var newVal = jToken.ToObject<UInt16>();
|
||||
if (oldValue is ushort oldVal && oldVal == newVal)
|
||||
{
|
||||
result = oldValue;
|
||||
return false;
|
||||
}
|
||||
result = newVal;
|
||||
return true;
|
||||
}
|
||||
|
||||
case DataTypeEnum.Int32:
|
||||
if (arrayLength > 1)
|
||||
{
|
||||
var newVal = jToken.ToObject<Int32[]>();
|
||||
if (oldValue is int[] oldArr && newVal.SequenceEqual(oldArr))
|
||||
{
|
||||
result = oldValue;
|
||||
return false;
|
||||
}
|
||||
result = newVal;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
var newVal = jToken.ToObject<Int32>();
|
||||
if (oldValue is int oldVal && oldVal == newVal)
|
||||
{
|
||||
result = oldValue;
|
||||
return false;
|
||||
}
|
||||
result = newVal;
|
||||
return true;
|
||||
}
|
||||
|
||||
case DataTypeEnum.UInt32:
|
||||
if (arrayLength > 1)
|
||||
{
|
||||
var newVal = jToken.ToObject<UInt32[]>();
|
||||
if (oldValue is uint[] oldArr && newVal.SequenceEqual(oldArr))
|
||||
{
|
||||
result = oldValue;
|
||||
return false;
|
||||
}
|
||||
result = newVal;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
var newVal = jToken.ToObject<UInt32>();
|
||||
if (oldValue is uint oldVal && oldVal == newVal)
|
||||
{
|
||||
result = oldValue;
|
||||
return false;
|
||||
}
|
||||
result = newVal;
|
||||
return true;
|
||||
}
|
||||
|
||||
case DataTypeEnum.Int64:
|
||||
if (arrayLength > 1)
|
||||
{
|
||||
var newVal = jToken.ToObject<Int64[]>();
|
||||
if (oldValue is long[] oldArr && newVal.SequenceEqual(oldArr))
|
||||
{
|
||||
result = oldValue;
|
||||
return false;
|
||||
}
|
||||
result = newVal;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
var newVal = jToken.ToObject<Int64>();
|
||||
if (oldValue is long oldVal && oldVal == newVal)
|
||||
{
|
||||
result = oldValue;
|
||||
return false;
|
||||
}
|
||||
result = newVal;
|
||||
return true;
|
||||
}
|
||||
|
||||
case DataTypeEnum.UInt64:
|
||||
if (arrayLength > 1)
|
||||
{
|
||||
var newVal = jToken.ToObject<UInt64[]>();
|
||||
if (oldValue is ulong[] oldArr && newVal.SequenceEqual(oldArr))
|
||||
{
|
||||
result = oldValue;
|
||||
return false;
|
||||
}
|
||||
result = newVal;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
var newVal = jToken.ToObject<UInt64>();
|
||||
if (oldValue is ulong oldVal && oldVal == newVal)
|
||||
{
|
||||
result = oldValue;
|
||||
return false;
|
||||
}
|
||||
result = newVal;
|
||||
return true;
|
||||
}
|
||||
|
||||
case DataTypeEnum.Float:
|
||||
if (arrayLength > 1)
|
||||
{
|
||||
var newVal = jToken.ToObject<Single[]>();
|
||||
if (oldValue is float[] oldArr && newVal.SequenceEqual(oldArr))
|
||||
{
|
||||
result = oldValue;
|
||||
return false;
|
||||
}
|
||||
result = newVal;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
var newVal = jToken.ToObject<Single>();
|
||||
if (oldValue is float oldVal && oldVal == newVal)
|
||||
{
|
||||
result = oldValue;
|
||||
return false;
|
||||
}
|
||||
result = newVal;
|
||||
return true;
|
||||
}
|
||||
|
||||
case DataTypeEnum.Double:
|
||||
if (arrayLength > 1)
|
||||
{
|
||||
var newVal = jToken.ToObject<Double[]>();
|
||||
if (oldValue is double[] oldArr && newVal.SequenceEqual(oldArr))
|
||||
{
|
||||
result = oldValue;
|
||||
return false;
|
||||
}
|
||||
result = newVal;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
var newVal = jToken.ToObject<Double>();
|
||||
if (oldValue is double oldVal && oldVal == newVal)
|
||||
{
|
||||
result = oldValue;
|
||||
return false;
|
||||
}
|
||||
result = newVal;
|
||||
return true;
|
||||
}
|
||||
case DataTypeEnum.Decimal:
|
||||
if (arrayLength > 1)
|
||||
{
|
||||
var newVal = jToken.ToObject<Decimal[]>();
|
||||
if (oldValue is decimal[] oldArr && newVal.SequenceEqual(oldArr))
|
||||
{
|
||||
result = oldValue;
|
||||
return false;
|
||||
}
|
||||
result = newVal;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
var newVal = jToken.ToObject<Decimal>();
|
||||
if (oldValue is decimal oldVal && oldVal == newVal)
|
||||
{
|
||||
result = oldValue;
|
||||
return false;
|
||||
}
|
||||
result = newVal;
|
||||
return true;
|
||||
}
|
||||
case DataTypeEnum.String:
|
||||
default:
|
||||
if (arrayLength > 1)
|
||||
{
|
||||
var newArr = new string[arrayLength];
|
||||
for (int i = 0; i < arrayLength; i++)
|
||||
{
|
||||
newArr[i] = jToken.ToObject<string>();
|
||||
}
|
||||
|
||||
if (oldValue is string[] oldArr && newArr.SequenceEqual(oldArr))
|
||||
{
|
||||
result = oldValue;
|
||||
return false;
|
||||
}
|
||||
result = newArr;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
var str = jToken.ToObject<string>();
|
||||
if (oldValue is string oldStr && oldStr == str)
|
||||
{
|
||||
result = oldStr;
|
||||
return false;
|
||||
}
|
||||
result = str;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endregion 获取对应数据类型的数据
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace ThingsGateway.Gateway.Application;
|
||||
|
||||
[AttributeUsage(AttributeTargets.Property, Inherited = true, AllowMultiple = false)]
|
||||
public class StringNotMemoryAttribute : ValidationAttribute
|
||||
{
|
||||
public StringNotMemoryAttribute()
|
||||
: base("The field {0} cannot be 'Memory'.")
|
||||
{
|
||||
}
|
||||
|
||||
public override bool IsValid(object value)
|
||||
{
|
||||
var str = value as string;
|
||||
return !string.Equals(str, "Memory", StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -77,7 +77,7 @@ public abstract class BusinessBaseWithCacheInterval : BusinessBaseWithCache
|
||||
{
|
||||
LogMessage?.LogInformation("Refresh variable");
|
||||
IdVariableRuntimes.Clear();
|
||||
IdVariableRuntimes.AddRange(GlobalData.GetEnableVariables().ToDictionary(a => a.Id));
|
||||
IdVariableRuntimes.AddRange(GlobalData.GetEnableVariables().Where(a => a.IsInternalMemoryVariable == false).ToDictionary(a => a.Id));
|
||||
CollectDevices = GlobalData.GetEnableDevices().Where(a => a.IsCollect == true).ToDictionary(a => a.Id);
|
||||
|
||||
VariableRuntimeGroups = IdVariableRuntimes.GroupBy(a => a.Value.BusinessGroup ?? string.Empty).ToDictionary(a => a.Key, a => a.Select(a => a.Value).ToList());
|
||||
|
||||
@@ -106,5 +106,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; }
|
||||
}
|
||||
|
||||
@@ -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 属性中
|
||||
@@ -540,7 +540,7 @@ public abstract partial class CollectBase : DriverBase
|
||||
// 如果成功,每个变量都读取一次最新值,再次比较写入值
|
||||
var successfulWriteNames = operResults.Where(a => a.Value.IsSuccess).Select(a => a.Key).ToHashSet();
|
||||
|
||||
var groups = writeInfoLists.Select(a => a.Key).Where(a => a.RpcWriteCheck && a.ProtectType != ProtectTypeEnum.WriteOnly && successfulWriteNames.Contains(a.Name) && a.VariableSource != null).GroupBy(a => a.VariableSource as VariableSourceRead).Where(a => a.Key != null).ToList();
|
||||
var groups = writeInfoLists.Select(a => a.Key).Where(a => a.RpcWriteCheck && a.ProtectType != ProtectTypeEnum.WriteOnly && successfulWriteNames.Contains(a.Name) && a.VariableSource != null).GroupBy(a => a.VariableSource as VariableSourceRead).Where(a => a.Key != null).ToArray();
|
||||
|
||||
await groups.ParallelForEachAsync(async (varRead, token) =>
|
||||
{
|
||||
@@ -594,7 +594,7 @@ public abstract partial class CollectBase : DriverBase
|
||||
// 根据写入表达式转换数据
|
||||
object data = deviceVariable.WriteExpressions.GetExpressionsResult(rawdata, LogMessage);
|
||||
// 将转换后的数据重新赋值给写入信息列表
|
||||
writeInfoLists[deviceVariable] = JToken.FromObject(data);
|
||||
writeInfoLists[deviceVariable] = JTokenUtil.GetJTokenFromObj(data);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -610,7 +610,7 @@ public abstract partial class CollectBase : DriverBase
|
||||
using var writeLock = await ReadWriteLock.WriterLockAsync(cancellationToken).ConfigureAwait(false);
|
||||
var list = writeInfoLists
|
||||
.Where(a => !results.Any(b => b.Key == a.Key.Name))
|
||||
.ToDictionary(item => item.Key, item => item.Value).ToArray();
|
||||
.ToArray();
|
||||
// 使用并发方式遍历写入信息列表,并进行异步写入操作
|
||||
await list.ParallelForEachAsync(async (writeInfo, cancellationToken) =>
|
||||
{
|
||||
@@ -662,7 +662,7 @@ public abstract partial class CollectBase : DriverBase
|
||||
// 根据写入表达式转换数据
|
||||
object data = deviceVariable.WriteExpressions.GetExpressionsResult(rawdata, LogMessage);
|
||||
// 将转换后的数据重新赋值给写入信息列表
|
||||
writeInfoLists[deviceVariable] = JToken.FromObject(data);
|
||||
writeInfoLists[deviceVariable] = JTokenUtil.GetJTokenFromObj(data);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
@@ -200,9 +200,8 @@ public abstract class CollectFoundationBase : CollectBase
|
||||
|
||||
// 创建用于存储操作结果的并发字典
|
||||
ConcurrentDictionary<string, OperResult> operResults = new();
|
||||
var list = writeInfoLists.ToArray();
|
||||
// 使用并发方式遍历写入信息列表,并进行异步写入操作
|
||||
await list.ParallelForEachAsync(async (writeInfo, cancellationToken) =>
|
||||
await writeInfoLists.ParallelForEachAsync(async (writeInfo, cancellationToken) =>
|
||||
{
|
||||
try
|
||||
{
|
||||
|
||||
@@ -37,6 +37,8 @@ public abstract class DriverBase : AsyncDisposableObject, IDriver
|
||||
|
||||
#region 属性
|
||||
|
||||
public virtual bool RefreshRuntimeAlways { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// 调试UI Type,如果不存在,返回null
|
||||
/// </summary>
|
||||
|
||||
@@ -62,28 +62,40 @@ public static class DynamicModelExtension
|
||||
}
|
||||
|
||||
|
||||
|
||||
if (GlobalData.IdDevices.TryGetValue(businessId, out var deviceRuntime))
|
||||
{
|
||||
if (deviceRuntime.Driver is BusinessBase businessBase && businessBase.DriverProperties is IBusinessPropertyAllVariableBase property)
|
||||
if (deviceRuntime.Driver is BusinessBase businessBase)
|
||||
{
|
||||
if (property.IsAllVariable)
|
||||
if (businessBase.DriverProperties is IBusinessPropertyAllVariableBase property && property.IsAllVariable)
|
||||
{
|
||||
// 检查是否存在对应的业务设备Id
|
||||
if (variableRuntime.VariablePropertys?.ContainsKey(businessId) == true)
|
||||
{
|
||||
variableRuntime.VariablePropertys[businessId].TryGetValue(propertyName, out var value);
|
||||
return value; // 返回属性值
|
||||
}
|
||||
else
|
||||
{
|
||||
return ThingsGatewayStringConverter.Default.Serialize(null, businessBase.VariablePropertys.GetValue(propertyName, false));
|
||||
}
|
||||
if (variableRuntime.IsInternalMemoryVariable == false)
|
||||
return GetVariableProperty(variableRuntime, businessId, propertyName, businessBase);
|
||||
}
|
||||
else if (businessBase.RefreshRuntimeAlways)
|
||||
{
|
||||
return GetVariableProperty(variableRuntime, businessId, propertyName, businessBase);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return null; // 未找到对应的业务设备Id,返回null
|
||||
|
||||
static string? GetVariableProperty(VariableRuntime variableRuntime, long businessId, string propertyName, BusinessBase businessBase)
|
||||
{
|
||||
// 检查是否存在对应的业务设备Id
|
||||
if (variableRuntime.VariablePropertys?.TryGetValue(businessId, out var kv) == true)
|
||||
{
|
||||
kv.TryGetValue(propertyName, out var value);
|
||||
return value; // 返回属性值
|
||||
}
|
||||
else
|
||||
{
|
||||
return ThingsGatewayStringConverter.Default.Serialize(null, businessBase.VariablePropertys.GetValue(propertyName, false));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -43,6 +43,7 @@ namespace ThingsGateway.Gateway.Application
|
||||
string PluginDirectory { get; }
|
||||
Dictionary<long, VariableRuntime> IdVariableRuntimes { get; }
|
||||
IDeviceThreadManage DeviceThreadManage { get; }
|
||||
bool RefreshRuntimeAlways { get; set; }
|
||||
|
||||
bool IsConnected();
|
||||
void PauseThread(bool pause);
|
||||
|
||||
@@ -43,6 +43,7 @@ public class Device : BaseDataEntity, IValidatableObject
|
||||
[AutoGenerateColumn(Visible = true, Filterable = true, Sortable = true)]
|
||||
[Required]
|
||||
[RegularExpression(@"^[^.]*$", ErrorMessage = "The field {0} cannot contain a dot ('.')")]
|
||||
[StringNotMemoryAttribute]
|
||||
public virtual string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -15,6 +15,8 @@ using Riok.Mapperly.Abstractions;
|
||||
using System.Collections.Concurrent;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
using ThingsGateway.NewLife.Json.Extension;
|
||||
|
||||
namespace ThingsGateway.Gateway.Application;
|
||||
|
||||
/// <summary>
|
||||
@@ -302,13 +304,11 @@ public class Variable : BaseDataEntity, IValidatableObject
|
||||
|
||||
#endregion 备用字段
|
||||
|
||||
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
|
||||
public virtual IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
|
||||
{
|
||||
if (string.IsNullOrEmpty(RegisterAddress) && string.IsNullOrEmpty(OtherMethod))
|
||||
{
|
||||
yield return new ValidationResult("Both RegisterAddress and OtherMethod cannot be empty or null.", new[] { nameof(OtherMethod), nameof(RegisterAddress) });
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,5 +23,5 @@ public enum PluginTypeEnum
|
||||
/// <summary>
|
||||
/// 业务
|
||||
/// </summary>
|
||||
Business,
|
||||
Business
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ public delegate void DelegateOnDeviceChanged(DeviceRuntime deviceRuntime, Device
|
||||
/// <param name="variableRuntime">变量运行时对象</param>
|
||||
/// <param name="variableData">变量数据对象</param>
|
||||
public delegate void VariableChangeEventHandler(VariableRuntime variableRuntime, VariableBasicData variableData);
|
||||
|
||||
/// <summary>
|
||||
/// 变量采集事件委托,用于通知变量进行采集时的事件
|
||||
/// </summary>
|
||||
@@ -100,10 +101,11 @@ public static class GlobalData
|
||||
return ReadOnlyIdDevices.WhereIf(dataScope != null && dataScope?.Count > 0, u => dataScope.Contains(u.Value.CreateOrgId))//在指定机构列表查询
|
||||
.WhereIf(dataScope?.Count == 0, u => u.Value.CreateUserId == UserManager.UserId).Select(a => a.Value);
|
||||
}
|
||||
|
||||
public static async Task<IEnumerable<VariableRuntime>> GetCurrentUserIdVariables()
|
||||
{
|
||||
var dataScope = await GlobalData.SysUserService.GetCurrentUserDataScopeAsync().ConfigureAwait(false);
|
||||
return IdVariables.WhereIf(dataScope != null && dataScope?.Count > 0, u => dataScope.Contains(u.Value.CreateOrgId))//在指定机构列表查询
|
||||
return IdVariables.Where(a => a.Value.IsInternalMemoryVariable == false).WhereIf(dataScope != null && dataScope?.Count > 0, u => dataScope.Contains(u.Value.CreateOrgId))//在指定机构列表查询
|
||||
.WhereIf(dataScope?.Count == 0, u => u.Value.CreateUserId == UserManager.UserId).Select(a => a.Value);
|
||||
}
|
||||
|
||||
@@ -117,7 +119,7 @@ public static class GlobalData
|
||||
public static async Task<IEnumerable<VariableRuntime>> GetCurrentUserAlarmEnableVariables()
|
||||
{
|
||||
var dataScope = await GlobalData.SysUserService.GetCurrentUserDataScopeAsync().ConfigureAwait(false);
|
||||
return AlarmEnableIdVariables.WhereIf(dataScope != null && dataScope?.Count > 0, u => dataScope.Contains(u.Value.CreateOrgId))//在指定机构列表查询
|
||||
return AlarmEnableIdVariables.Where(a => a.Value.IsInternalMemoryVariable == false).WhereIf(dataScope != null && dataScope?.Count > 0, u => dataScope.Contains(u.Value.CreateOrgId))//在指定机构列表查询
|
||||
.WhereIf(dataScope?.Count == 0, u => u.Value.CreateUserId == UserManager.UserId).Select(a => a.Value);
|
||||
}
|
||||
|
||||
@@ -125,9 +127,15 @@ public static class GlobalData
|
||||
{
|
||||
if (GlobalData.IdDevices.TryGetValue(businessDeviceId, out var deviceRuntime))
|
||||
{
|
||||
if (deviceRuntime.Driver?.DriverProperties is IBusinessPropertyAllVariableBase property)
|
||||
|
||||
if (deviceRuntime.Driver is BusinessBase businessBase)
|
||||
{
|
||||
if (property.IsAllVariable)
|
||||
if (businessBase.DriverProperties is IBusinessPropertyAllVariableBase property && property.IsAllVariable)
|
||||
{
|
||||
if (a.IsInternalMemoryVariable == false)
|
||||
return true;
|
||||
}
|
||||
else if (businessBase.RefreshRuntimeAlways)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
@@ -141,7 +149,46 @@ public static class GlobalData
|
||||
|
||||
return a.VariablePropertys?.ContainsKey(businessDeviceId) == true;
|
||||
}
|
||||
public static DeviceRuntime[] GetAllVariableBusinessDeviceRuntime()
|
||||
{
|
||||
var channelDevice = GlobalData.IdDevices.Where(a =>
|
||||
{
|
||||
if (a.Value.Driver is BusinessBase businessBase)
|
||||
{
|
||||
if (businessBase.DriverProperties is IBusinessPropertyAllVariableBase property && property.IsAllVariable)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else if (businessBase.RefreshRuntimeAlways)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
|
||||
}).Select(a => a.Value).ToArray();
|
||||
return channelDevice;
|
||||
}
|
||||
public static IDriver[] GetAllVariableBusinessDriver()
|
||||
{
|
||||
var channelDevice = GlobalData.IdDevices.Where(a =>
|
||||
{
|
||||
if (a.Value.Driver is BusinessBase businessBase)
|
||||
{
|
||||
if (businessBase.DriverProperties is IBusinessPropertyAllVariableBase property && property.IsAllVariable)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else if (businessBase.RefreshRuntimeAlways)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
|
||||
}).Select(a => a.Value.Driver).ToArray();
|
||||
return channelDevice;
|
||||
}
|
||||
/// <summary>
|
||||
/// 只读的通道字典,提供对通道的只读访问
|
||||
/// </summary>
|
||||
@@ -162,6 +209,32 @@ public static class GlobalData
|
||||
return null;
|
||||
}
|
||||
|
||||
public static VariableRuntime GetVariable(string variableName)
|
||||
{
|
||||
|
||||
if (string.IsNullOrEmpty(variableName))
|
||||
return null;
|
||||
var names = variableName.Split('.');
|
||||
if (names.Length > 2)
|
||||
return null;
|
||||
|
||||
if (names.Length == 1)
|
||||
{
|
||||
if (MemoryVariables.TryGetValue(names[0], out var variable))
|
||||
{
|
||||
return variable;
|
||||
}
|
||||
}
|
||||
else if (Devices.TryGetValue(names[0], out var device))
|
||||
{
|
||||
if (device.VariableRuntimes.TryGetValue(names[1], out var variable))
|
||||
{
|
||||
return variable;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static IEnumerable<DeviceRuntime> GetEnableDevices()
|
||||
{
|
||||
var idSet = GetRedundantDeviceIds();
|
||||
@@ -189,7 +262,7 @@ public static class GlobalData
|
||||
|
||||
public static IEnumerable<VariableRuntime> GetEnableVariables()
|
||||
{
|
||||
return IdDevices.SelectMany(a => a.Value.VariableRuntimes).Where(a => a.Value?.Enable == true).Select(a => a.Value);
|
||||
return IdVariables.Where(a => a.Value.DeviceRuntime?.Enable != false && a.Value.DeviceRuntime?.ChannelRuntime?.Enable != false && a.Value?.Enable == true).Select(a => a.Value);
|
||||
}
|
||||
|
||||
public static bool TryGetDeviceThreadManage(DeviceRuntime deviceRuntime, out IDeviceThreadManage deviceThreadManage)
|
||||
@@ -483,6 +556,9 @@ public static class GlobalData
|
||||
/// </summary>
|
||||
internal static ConcurrentDictionary<long, VariableRuntime> IdVariables { get; } = new();
|
||||
|
||||
internal static ConcurrentDictionary<string, VariableRuntime> MemoryVariables { get; } = new();
|
||||
public static IReadOnlyDictionary<string, VariableRuntime> ReadOnlyMemoryVariables => MemoryVariables;
|
||||
|
||||
/// <summary>
|
||||
/// 实时报警列表
|
||||
/// </summary>
|
||||
|
||||
@@ -131,8 +131,10 @@ public class VariableBasicData
|
||||
//[System.Text.Json.Serialization.JsonIgnore]
|
||||
//[Newtonsoft.Json.JsonIgnore]
|
||||
//public DeviceBasicData DeviceRuntime { get; set; }
|
||||
|
||||
/// <inheritdoc cref="Variable.DeviceId"/>
|
||||
public long DeviceId { get; set; }
|
||||
|
||||
/// <inheritdoc cref="VariableRuntime.LastErrorMessage"/>
|
||||
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
|
||||
[System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull)]
|
||||
@@ -142,6 +144,11 @@ public class VariableBasicData
|
||||
[System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull)]
|
||||
public string? RegisterAddress { get; set; }
|
||||
|
||||
/// <inheritdoc cref="Variable.OtherMethod"/>
|
||||
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
|
||||
[System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull)]
|
||||
public string? OtherMethod { get; set; }
|
||||
|
||||
/// <inheritdoc cref="Variable.Unit"/>
|
||||
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
|
||||
[System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull)]
|
||||
@@ -190,5 +197,6 @@ public class VariableBasicData
|
||||
|
||||
/// <inheritdoc cref="VariableRuntime.ValueInited"/>
|
||||
public bool ValueInited { get; set; }
|
||||
|
||||
/// <inheritdoc cref="VariableRuntime.IsMemoryVariable"/>
|
||||
public bool IsMemoryVariable { get; set; }
|
||||
}
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace ThingsGateway.Gateway.Application;
|
||||
|
||||
public interface IMemoryVariableRpc
|
||||
{
|
||||
OperResult MemoryVariableRpc(string value, CancellationToken cancellationToken = default);
|
||||
}
|
||||
@@ -14,6 +14,7 @@ using Newtonsoft.Json.Linq;
|
||||
|
||||
using Riok.Mapperly.Abstractions;
|
||||
|
||||
|
||||
#if !Management
|
||||
using ThingsGateway.Gateway.Application.Extensions;
|
||||
#endif
|
||||
@@ -32,6 +33,13 @@ public partial class VariableRuntime : Variable
|
||||
IDisposable
|
||||
#endif
|
||||
{
|
||||
|
||||
|
||||
[AutoGenerateColumn(Ignore = true)]
|
||||
public virtual bool IsMemoryVariable { get => _isMemoryVariable; set => _isMemoryVariable = value; }
|
||||
[AutoGenerateColumn(Ignore = true)]
|
||||
public virtual bool IsInternalMemoryVariable { get => _isInternalMemoryVariable; set => _isInternalMemoryVariable = value; }
|
||||
|
||||
[AutoGenerateColumn(Ignore = true)]
|
||||
public bool ValueInited { get => _valueInited; set => _valueInited = value; }
|
||||
|
||||
@@ -74,15 +82,22 @@ public partial class VariableRuntime : Variable
|
||||
public object RawValue { get => rawValue; set => rawValue = value; }
|
||||
|
||||
|
||||
|
||||
#if !Management
|
||||
|
||||
|
||||
[Newtonsoft.Json.JsonIgnore]
|
||||
[System.Text.Json.Serialization.JsonIgnore]
|
||||
[AutoGenerateColumn(Ignore = true)]
|
||||
public TouchSocket.Core.ILog? LogMessage => DeviceRuntime?.Driver?.LogMessage;
|
||||
|
||||
/// <summary>
|
||||
/// 所在采集设备
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonIgnore]
|
||||
[System.Text.Json.Serialization.JsonIgnore]
|
||||
[AutoGenerateColumn(Ignore = true)]
|
||||
public DeviceRuntime? DeviceRuntime { get => deviceRuntime; set => deviceRuntime = value; }
|
||||
public DeviceRuntime? DeviceRuntime { get => deviceRuntime; private set => deviceRuntime = value; }
|
||||
|
||||
/// <summary>
|
||||
/// VariableSource
|
||||
@@ -239,6 +254,8 @@ public partial class VariableRuntime : Variable
|
||||
#pragma warning restore CS0169
|
||||
#pragma warning restore CS0414
|
||||
private bool _valueInited;
|
||||
private bool _isMemoryVariable;
|
||||
private bool _isInternalMemoryVariable;
|
||||
|
||||
private object _value;
|
||||
private object lastSetValue;
|
||||
@@ -272,7 +289,7 @@ public partial class VariableRuntime : Variable
|
||||
{
|
||||
try
|
||||
{
|
||||
var data = ReadExpressions.GetExpressionsResult(RawValue, DeviceRuntime?.Driver?.LogMessage);
|
||||
var data = ReadExpressions.GetExpressionsResult(RawValue, LogMessage);
|
||||
Set(data, dateTime);
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -291,7 +308,7 @@ public partial class VariableRuntime : Variable
|
||||
}
|
||||
if (oldMessage != _lastErrorMessage)
|
||||
{
|
||||
DeviceRuntime?.Driver?.LogMessage?.LogWarning(_lastErrorMessage);
|
||||
LogMessage?.LogWarning(_lastErrorMessage);
|
||||
}
|
||||
return new($"{Name} Conversion expression failed", ex);
|
||||
}
|
||||
@@ -394,6 +411,12 @@ public partial class VariableRuntime : Variable
|
||||
|
||||
DeviceRuntime = deviceRuntime;
|
||||
|
||||
if (deviceRuntime == null)
|
||||
{
|
||||
GlobalData.MemoryVariables.Remove(Name);
|
||||
GlobalData.MemoryVariables.TryAdd(Name, this);
|
||||
}
|
||||
|
||||
DeviceRuntime?.VariableRuntimes?.TryAdd(Name, this);
|
||||
GlobalData.IdVariables.Remove(Id);
|
||||
GlobalData.IdVariables.TryAdd(Id, this);
|
||||
@@ -407,7 +430,10 @@ public partial class VariableRuntime : Variable
|
||||
public void Dispose()
|
||||
{
|
||||
DeviceRuntime?.VariableRuntimes?.Remove(Name);
|
||||
|
||||
if (DeviceRuntime == null)
|
||||
{
|
||||
GlobalData.MemoryVariables.Remove(Name);
|
||||
}
|
||||
GlobalData.IdVariables.Remove(Id);
|
||||
|
||||
GlobalData.AlarmEnableIdVariables.Remove(Id);
|
||||
@@ -422,7 +448,7 @@ public partial class VariableRuntime : Variable
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async ValueTask<IOperResult> RpcAsync(string value, string executive = "brower", CancellationToken cancellationToken = default)
|
||||
public virtual async ValueTask<IOperResult> RpcAsync(string value, string executive = "brower", CancellationToken cancellationToken = default)
|
||||
{
|
||||
var data = await GlobalData.RpcService.InvokeDeviceMethodAsync(executive, new Dictionary<string, Dictionary<string, string>>()
|
||||
{
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
using ThingsGateway.Common.Extension;
|
||||
using ThingsGateway.Gateway.Application.Extensions;
|
||||
using ThingsGateway.NewLife.Extension;
|
||||
|
||||
@@ -110,7 +109,7 @@ internal sealed class AlarmTask : IDisposable
|
||||
if (tag.AlarmPropertys.CustomAlarmEnable) // 检查是否启用了自定义报警功能
|
||||
{
|
||||
// 调用变量的CustomAlarmCode属性的GetExpressionsResult方法,传入变量的值,获取报警表达式的计算结果
|
||||
var result = tag.AlarmPropertys.CustomAlarmCode.GetExpressionsResult(tag.Value, tag.DeviceRuntime?.Driver?.LogMessage);
|
||||
var result = tag.AlarmPropertys.CustomAlarmCode.GetExpressionsResult(tag.Value, tag.LogMessage);
|
||||
|
||||
if (result is bool boolResult) // 检查计算结果是否为布尔类型
|
||||
{
|
||||
@@ -227,7 +226,7 @@ internal sealed class AlarmTask : IDisposable
|
||||
if (!string.IsNullOrEmpty(ex))
|
||||
{
|
||||
// 如果存在报警约束表达式,则计算表达式结果,以确定是否触发报警事件
|
||||
var data = ex.GetExpressionsResult(item.Value, item.DeviceRuntime?.Driver?.LogMessage);
|
||||
var data = ex.GetExpressionsResult(item.Value, item.LogMessage);
|
||||
if (data is bool result)
|
||||
{
|
||||
if (result)
|
||||
@@ -487,6 +486,11 @@ internal sealed class AlarmTask : IDisposable
|
||||
|
||||
#endregion 核心实现
|
||||
|
||||
ParallelOptions ParallelOptions = new()
|
||||
{
|
||||
MaxDegreeOfParallelism = Math.Max(1, Environment.ProcessorCount / 2)
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// 执行工作任务,对设备变量进行报警分析。
|
||||
/// </summary>
|
||||
@@ -499,32 +503,36 @@ internal sealed class AlarmTask : IDisposable
|
||||
if (!GlobalData.StartBusinessChannelEnable)
|
||||
return;
|
||||
|
||||
//Stopwatch stopwatch = Stopwatch.StartNew();
|
||||
// 遍历设备变量列表
|
||||
//System.Diagnostics.Stopwatch stopwatch = System.Diagnostics.Stopwatch.StartNew();
|
||||
|
||||
if (scheduledTask.Period < 100 && scheduledTask.Period > 1 && GlobalData.AlarmEnableIdVariables.Count > 50000)
|
||||
{
|
||||
scheduledTask.Change(100, 100);
|
||||
}
|
||||
|
||||
// 遍历设备变量列表
|
||||
if (!GlobalData.AlarmEnableIdVariables.IsEmpty)
|
||||
{
|
||||
var list = GlobalData.AlarmEnableIdVariables.Select(a => a.Value).ToArray();
|
||||
list.ParallelForEach((item, state, index) =>
|
||||
// 使用 Parallel.ForEach 执行指定的操作
|
||||
Parallel.ForEach(GlobalData.AlarmEnableIdVariables, ParallelOptions, (item, state, index) =>
|
||||
{
|
||||
{
|
||||
// 如果取消请求已经被触发,则结束任务
|
||||
if (cancellation.IsCancellationRequested)
|
||||
return;
|
||||
// 如果取消请求已经被触发,则结束任务
|
||||
if (cancellation.IsCancellationRequested)
|
||||
return;
|
||||
|
||||
// 如果该变量的报警功能未启用,则跳过该变量
|
||||
if (!item.AlarmEnable)
|
||||
return;
|
||||
// 如果该变量的报警功能未启用,则跳过该变量
|
||||
if (!item.Value.AlarmEnable)
|
||||
return;
|
||||
|
||||
// 如果该变量离线,则跳过该变量
|
||||
if (!item.IsOnline)
|
||||
return;
|
||||
// 如果该变量离线,则跳过该变量
|
||||
if (!item.Value.IsOnline)
|
||||
return;
|
||||
|
||||
// 对该变量进行报警分析
|
||||
AlarmAnalysis(item);
|
||||
}
|
||||
// 对该变量进行报警分析
|
||||
AlarmAnalysis(item.Value);
|
||||
});
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
//if (scheduledTask.Period != 5000)
|
||||
|
||||
@@ -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)
|
||||
@@ -573,8 +573,7 @@ internal sealed class DeviceThreadManage : IAsyncDisposable, IDeviceThreadManage
|
||||
{
|
||||
//传入变量
|
||||
//newDeviceRuntime.VariableRuntimes.ParallelForEach(a => a.Value.SafeDispose());
|
||||
var list = deviceRuntime.VariableRuntimes.Select(a => a.Value).ToArray();
|
||||
list.ParallelForEach(a => a.Init(newDeviceRuntime));
|
||||
deviceRuntime.VariableRuntimes.ParallelForEach(a => a.Value.Init(newDeviceRuntime));
|
||||
GlobalData.VariableRuntimeDispatchService.Dispatch(null);
|
||||
}
|
||||
|
||||
|
||||
@@ -54,7 +54,7 @@ internal sealed class GatewayMonitorHostedService : BackgroundService, IGatewayM
|
||||
{
|
||||
item.Init(channelRuntime);
|
||||
|
||||
var varRuntimes = variableRuntimes.Where(x => x.DeviceId == item.Id).ToArray();
|
||||
var varRuntimes = variableRuntimes.Where(x => x.DeviceId == item.Id);
|
||||
|
||||
varRuntimes.ParallelForEach(varItem => varItem.Init(item));
|
||||
}
|
||||
|
||||
@@ -92,7 +92,7 @@ public partial class ManagementTask : AsyncDisposableObject
|
||||
var tcpDmtpClient = new TcpDmtpClient();
|
||||
var config = new TouchSocketConfig()
|
||||
.SetRemoteIPHost(_managementOptions.ServerUri)
|
||||
.SetAdapterOption(new AdapterOption() { MaxPackageSize = 1024 * 1024 * 1024 })
|
||||
.SetAdapterOption(a => a.MaxPackageSize = 1024 * 1024 * 1024)
|
||||
.SetDmtpOption(a => a.VerifyToken = _managementOptions.VerifyToken)
|
||||
.ConfigureContainer(a =>
|
||||
{
|
||||
@@ -156,7 +156,7 @@ public partial class ManagementTask : AsyncDisposableObject
|
||||
var tcpDmtpService = new TcpDmtpService();
|
||||
var config = new TouchSocketConfig()
|
||||
.SetListenIPHosts(_managementOptions.ServerUri)
|
||||
.SetAdapterOption(new AdapterOption() { MaxPackageSize = 1024 * 1024 * 1024 })
|
||||
.SetAdapterOption(a => a.MaxPackageSize = 1024 * 1024 * 1024)
|
||||
.SetDmtpOption(a => a.VerifyToken = _managementOptions.VerifyToken)
|
||||
.ConfigureContainer(a =>
|
||||
{
|
||||
|
||||
@@ -331,7 +331,7 @@ internal sealed class RedundancyTask : IRpcDriver, IAsyncDisposable
|
||||
var tcpDmtpClient = new TcpDmtpClient();
|
||||
var config = new TouchSocketConfig()
|
||||
.SetRemoteIPHost(redundancy.MasterUri)
|
||||
.SetAdapterOption(new AdapterOption() { MaxPackageSize = 0x20000000 })
|
||||
.SetAdapterOption(a => a.MaxPackageSize = 0x20000000)
|
||||
.SetDmtpOption(a => a.VerifyToken = redundancy.VerifyToken)
|
||||
.ConfigureContainer(a =>
|
||||
{
|
||||
@@ -376,7 +376,7 @@ internal sealed class RedundancyTask : IRpcDriver, IAsyncDisposable
|
||||
var tcpDmtpService = new TcpDmtpService();
|
||||
var config = new TouchSocketConfig()
|
||||
.SetListenIPHosts(redundancy.MasterUri)
|
||||
.SetAdapterOption(new AdapterOption() { MaxPackageSize = 0x20000000 })
|
||||
.SetAdapterOption(a => a.MaxPackageSize = 0x20000000)
|
||||
.SetDmtpOption(a => a.VerifyToken = redundancy.VerifyToken)
|
||||
.ConfigureContainer(a =>
|
||||
{
|
||||
@@ -479,7 +479,7 @@ internal sealed class RedundancyTask : IRpcDriver, IAsyncDisposable
|
||||
{
|
||||
int maxBatchSize = GetBatchSize() / 10;
|
||||
|
||||
var groups = GlobalData.IdVariables.Select(a => a.Value).GroupBy(a => a.DeviceRuntime);
|
||||
var groups = GlobalData.IdVariables.Select(a => a.Value).GroupBy(a => a.DeviceRuntime).Where(a => a.Key != null);
|
||||
|
||||
var channelBatch = new HashSet<Channel>();
|
||||
var deviceBatch = new HashSet<Device>();
|
||||
|
||||
@@ -30,6 +30,10 @@ internal sealed class RpcService : IRpcService
|
||||
{
|
||||
private readonly ConcurrentQueue<RpcLog> _logQueues = new();
|
||||
private readonly RpcLogOptions? _rpcLogOptions;
|
||||
private SqlSugarClient _db = DbContext.GetDB<RpcLog>(); // 创建一个新的数据库上下文实例
|
||||
|
||||
private IStringLocalizer Localizer { get; }
|
||||
|
||||
/// <inheritdoc cref="RpcService"/>
|
||||
public RpcService(IStringLocalizer<RpcService> localizer)
|
||||
{
|
||||
@@ -38,50 +42,118 @@ internal sealed class RpcService : IRpcService
|
||||
_rpcLogOptions = App.GetOptions<RpcLogOptions>();
|
||||
}
|
||||
|
||||
private IStringLocalizer Localizer { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<Dictionary<string, Dictionary<string, IOperResult>>> InvokeDeviceMethodAsync(string sourceDes, Dictionary<string, Dictionary<string, string>> deviceDatas, CancellationToken cancellationToken = default)
|
||||
public async Task<Dictionary<string, Dictionary<string, IOperResult>>> InvokeDeviceMethodAsync(
|
||||
string sourceDes,
|
||||
Dictionary<string, Dictionary<string, string>> deviceDatas,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
// 初始化用于存储将要写入的变量和方法的字典
|
||||
Dictionary<IRpcDriver, Dictionary<VariableRuntime, JToken>> writeVariables = new();
|
||||
Dictionary<IRpcDriver, Dictionary<VariableRuntime, JToken>> writeMethods = new();
|
||||
Dictionary<VariableRuntime, string> memoryVariables = new();
|
||||
|
||||
// 用于存储结果的并发字典
|
||||
ConcurrentDictionary<string, Dictionary<string, IOperResult>> results = new();
|
||||
deviceDatas.ForEach(a => results.TryAdd(a.Key, new()));
|
||||
|
||||
// 对每个要操作的变量进行检查和处理(内存变量)
|
||||
foreach (var item in deviceDatas.Where(a => a.Key.IsNullOrEmpty() || a.Key.Equals("Memory", StringComparison.OrdinalIgnoreCase)).SelectMany(a => a.Value))
|
||||
{
|
||||
// 查找变量是否存在
|
||||
if (!(GlobalData.MemoryVariables.TryGetValue(item.Key, out var tag) &&
|
||||
tag is IMemoryVariableRpc memoryVariableRuntime))
|
||||
{
|
||||
// 如果变量不存在,则添加错误信息到结果中并继续下一个变量的处理
|
||||
results["Memory"].TryAdd(item.Key, new OperResult(Localizer["VariableNotNull", item.Key]));
|
||||
continue;
|
||||
}
|
||||
|
||||
// 检查变量的保护类型和远程写入权限
|
||||
if (tag.ProtectType == ProtectTypeEnum.ReadOnly)
|
||||
{
|
||||
results["Memory"].TryAdd(item.Key, new OperResult(Localizer["VariableReadOnly", item.Key]));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!tag.RpcWriteEnable)
|
||||
{
|
||||
results["Memory"].TryAdd(item.Key, new OperResult(Localizer["VariableWriteDisable", item.Key]));
|
||||
continue;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var start = DateTime.Now;
|
||||
|
||||
var variableResult = memoryVariableRuntime.MemoryVariableRpc(item.Value, cancellationToken);
|
||||
var end = DateTime.Now;
|
||||
|
||||
string operObj = tag.Name;
|
||||
string parJson = deviceDatas["Memory"][tag.Name];
|
||||
|
||||
if (!variableResult.IsSuccess || _rpcLogOptions.SuccessLog)
|
||||
{
|
||||
_logQueues.Enqueue(
|
||||
new RpcLog()
|
||||
{
|
||||
LogTime = start,
|
||||
ExecutionTime = (int)(end - start).TotalMilliseconds,
|
||||
OperateMessage = variableResult.IsSuccess ? null : variableResult.ToString(),
|
||||
IsSuccess = variableResult.IsSuccess,
|
||||
OperateMethod = AppResource.WriteVariable,
|
||||
OperateDevice = string.Empty,
|
||||
OperateObject = operObj,
|
||||
OperateSource = sourceDes,
|
||||
ParamJson = parJson,
|
||||
ResultJson = null
|
||||
});
|
||||
}
|
||||
|
||||
// 不返回详细错误
|
||||
if (!variableResult.IsSuccess)
|
||||
{
|
||||
var result1 = variableResult;
|
||||
result1.Exception = null;
|
||||
variableResult = result1;
|
||||
}
|
||||
|
||||
results["Memory"].Add(tag.Name, variableResult);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// 将异常信息添加到结果字典中
|
||||
results["Memory"].Add(tag.Name, new OperResult(ex));
|
||||
}
|
||||
}
|
||||
|
||||
var deviceDict = GlobalData.Devices;
|
||||
|
||||
// 对每个要操作的变量进行检查和处理
|
||||
foreach (var deviceData in deviceDatas)
|
||||
// 对每个要操作的变量进行检查和处理(设备变量)
|
||||
foreach (var deviceData in deviceDatas.Where(a => (!a.Key.IsNullOrEmpty() && !a.Key.Equals("Memory", StringComparison.OrdinalIgnoreCase))))
|
||||
{
|
||||
// 查找设备是否存在
|
||||
if (!deviceDict.TryGetValue(deviceData.Key, out var device))
|
||||
{
|
||||
// 如果设备不存在,则添加错误信息到结果中并继续下一个设备的处理
|
||||
deviceData.Value.ForEach(a =>
|
||||
results[deviceData.Key].TryAdd(a.Key, new OperResult(Localizer["DeviceNotNull", deviceData.Key]))
|
||||
);
|
||||
results[deviceData.Key].TryAdd(a.Key, new OperResult(Localizer["DeviceNotNull", deviceData.Key])));
|
||||
continue;
|
||||
}
|
||||
|
||||
// 查找变量对应的设备
|
||||
var collect = device.Driver as IRpcDriver;
|
||||
collect ??= device.RpcDriver;
|
||||
var collect = device.Driver as IRpcDriver ?? device.RpcDriver;
|
||||
if (collect == null)
|
||||
{
|
||||
// 如果设备不存在,则添加错误信息到结果中并继续下一个设备的处理
|
||||
deviceData.Value.ForEach(a =>
|
||||
results[deviceData.Key].TryAdd(a.Key, new OperResult(Localizer["DriverNotNull", deviceData.Key]))
|
||||
);
|
||||
results[deviceData.Key].TryAdd(a.Key, new OperResult(Localizer["DriverNotNull", deviceData.Key])));
|
||||
continue;
|
||||
}
|
||||
// 检查设备状态,如果设备处于暂停状态,则添加相应的错误信息到结果中并继续下一个变量的处理
|
||||
|
||||
// 检查设备状态
|
||||
if (device.DeviceStatus == DeviceStatusEnum.Pause)
|
||||
{
|
||||
deviceData.Value.ForEach(a =>
|
||||
results[deviceData.Key].TryAdd(a.Key, new OperResult(Localizer["DevicePause", deviceData.Key]))
|
||||
);
|
||||
results[deviceData.Key].TryAdd(a.Key, new OperResult(Localizer["DevicePause", deviceData.Key])));
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -90,7 +162,6 @@ internal sealed class RpcService : IRpcService
|
||||
// 查找变量是否存在
|
||||
if (!device.VariableRuntimes.TryGetValue(item.Key, out var tag))
|
||||
{
|
||||
// 如果变量不存在,则添加错误信息到结果中并继续下一个变量的处理
|
||||
results[deviceData.Key].TryAdd(item.Key, new OperResult(Localizer["VariableNotNull", item.Key]));
|
||||
continue;
|
||||
}
|
||||
@@ -101,6 +172,7 @@ internal sealed class RpcService : IRpcService
|
||||
results[deviceData.Key].TryAdd(item.Key, new OperResult(Localizer["VariableReadOnly", item.Key]));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!tag.RpcWriteEnable)
|
||||
{
|
||||
results[deviceData.Key].TryAdd(item.Key, new OperResult(Localizer["VariableWriteDisable", item.Key]));
|
||||
@@ -110,6 +182,7 @@ internal sealed class RpcService : IRpcService
|
||||
JToken tagValue = JTokenUtil.GetJTokenFromString(item.Value);
|
||||
bool isOtherMethodEmpty = string.IsNullOrEmpty(tag.OtherMethod);
|
||||
var collection = isOtherMethodEmpty ? writeVariables : writeMethods;
|
||||
|
||||
if (collection.TryGetValue(collect, out var value))
|
||||
{
|
||||
value.Add(tag, tagValue);
|
||||
@@ -121,26 +194,26 @@ internal sealed class RpcService : IRpcService
|
||||
}
|
||||
}
|
||||
}
|
||||
var writeVariableArrays = writeVariables.ToArray();
|
||||
|
||||
// 使用并行方式写入变量
|
||||
var writeVariableArrays = writeVariables;
|
||||
await writeVariableArrays.ParallelForEachAsync(async (driverData, cancellationToken) =>
|
||||
{
|
||||
try
|
||||
{
|
||||
var start = DateTime.Now;
|
||||
// 调用设备的写入方法
|
||||
var result = await driverData.Key.InVokeWriteAsync(driverData.Value, cancellationToken).ConfigureAwait(false);
|
||||
var end = DateTime.Now;
|
||||
// 写入日志
|
||||
|
||||
foreach (var resultItem in result)
|
||||
{
|
||||
foreach (var variableResult in resultItem.Value)
|
||||
{
|
||||
string operObj = variableResult.Key;
|
||||
|
||||
string parJson = deviceDatas[resultItem.Key][variableResult.Key];
|
||||
|
||||
if (!variableResult.Value.IsSuccess || _rpcLogOptions.SuccessLog)
|
||||
{
|
||||
_logQueues.Enqueue(
|
||||
new RpcLog()
|
||||
{
|
||||
@@ -154,10 +227,9 @@ internal sealed class RpcService : IRpcService
|
||||
OperateSource = sourceDes,
|
||||
ParamJson = parJson,
|
||||
ResultJson = null
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
// 不返回详细错误
|
||||
if (!variableResult.Value.IsSuccess)
|
||||
{
|
||||
var result1 = variableResult.Value;
|
||||
@@ -166,44 +238,42 @@ internal sealed class RpcService : IRpcService
|
||||
}
|
||||
}
|
||||
|
||||
// 将结果添加到结果字典中
|
||||
results[resultItem.Key].AddRange(resultItem.Value);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// 将异常信息添加到结果字典中
|
||||
foreach (var item in driverData.Value)
|
||||
{
|
||||
results[item.Key.DeviceName].Add(item.Key.Name, new OperResult(ex));
|
||||
}
|
||||
}
|
||||
}, cancellationToken).ConfigureAwait(false);
|
||||
var writeMethodArrays = writeMethods.ToArray();
|
||||
|
||||
// 使用并行方式执行方法
|
||||
var writeMethodArrays = writeMethods;
|
||||
await writeMethodArrays.ParallelForEachAsync(async (driverData, cancellationToken) =>
|
||||
{
|
||||
try
|
||||
{
|
||||
var start = DateTime.Now;
|
||||
// 调用设备的写入方法
|
||||
var result = await driverData.Key.InvokeMethodAsync(driverData.Value, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
Dictionary<string, string> operateMethods = driverData.Value.Select(a => a.Key).ToDictionary(a => a.Name, a => a.OtherMethod!);
|
||||
Dictionary<string, string> operateMethods = driverData.Value
|
||||
.Select(a => a.Key)
|
||||
.ToDictionary(a => a.Name, a => a.OtherMethod!);
|
||||
|
||||
var end = DateTime.Now;
|
||||
|
||||
// 写入日志
|
||||
foreach (var resultItem in result)
|
||||
{
|
||||
foreach (var variableResult in resultItem.Value)
|
||||
{
|
||||
string operObj = variableResult.Key;
|
||||
|
||||
string parJson = deviceDatas[resultItem.Key][variableResult.Key];
|
||||
|
||||
// 写入日志
|
||||
if (!variableResult.Value.IsSuccess || _rpcLogOptions.SuccessLog)
|
||||
{
|
||||
_logQueues.Enqueue(
|
||||
new RpcLog()
|
||||
{
|
||||
@@ -216,11 +286,12 @@ internal sealed class RpcService : IRpcService
|
||||
OperateObject = operObj,
|
||||
OperateSource = sourceDes,
|
||||
ParamJson = parJson?.ToString(),
|
||||
ResultJson = variableResult.Value is IOperResult<object> operResult ? operResult.Content?.ToSystemTextJsonString() : string.Empty
|
||||
}
|
||||
);
|
||||
ResultJson = variableResult.Value is IOperResult<object> operResult
|
||||
? operResult.Content?.ToSystemTextJsonString()
|
||||
: string.Empty
|
||||
});
|
||||
}
|
||||
|
||||
// 不返回详细错误
|
||||
if (!variableResult.Value.IsSuccess)
|
||||
{
|
||||
var result1 = variableResult.Value;
|
||||
@@ -234,7 +305,6 @@ internal sealed class RpcService : IRpcService
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// 将异常信息添加到结果字典中
|
||||
foreach (var item in driverData.Value)
|
||||
{
|
||||
results[item.Key.DeviceName].Add(item.Key.Name, new OperResult(ex));
|
||||
@@ -246,8 +316,6 @@ internal sealed class RpcService : IRpcService
|
||||
return new(results);
|
||||
}
|
||||
|
||||
private SqlSugarClient _db = DbContext.GetDB<RpcLog>(); // 创建一个新的数据库上下文实例
|
||||
|
||||
/// <summary>
|
||||
/// 异步执行RPC日志插入操作的方法。
|
||||
/// </summary>
|
||||
@@ -258,11 +326,12 @@ internal sealed class RpcService : IRpcService
|
||||
{
|
||||
try
|
||||
{
|
||||
var data = _logQueues.ToListWithDequeue(); // 从日志队列中获取数据
|
||||
var data = _logQueues.ToListWithDequeue();
|
||||
if (data.Count > 0)
|
||||
{
|
||||
// 将数据插入到数据库中
|
||||
await _db.InsertableWithAttr(data).ExecuteCommandAsync(appLifetime.ApplicationStopping).ConfigureAwait(false);
|
||||
await _db.InsertableWithAttr(data)
|
||||
.ExecuteCommandAsync(appLifetime.ApplicationStopping)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -271,7 +340,7 @@ internal sealed class RpcService : IRpcService
|
||||
}
|
||||
finally
|
||||
{
|
||||
await Task.Delay(3000).ConfigureAwait(false); // 在finally块中等待一段时间后继续下一次循环
|
||||
await Task.Delay(3000).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ using ThingsGateway.Blazor.Diagrams.Core;
|
||||
using ThingsGateway.Blazor.Diagrams.Core.Anchors;
|
||||
using ThingsGateway.Blazor.Diagrams.Core.Geometry;
|
||||
using ThingsGateway.Blazor.Diagrams.Core.Models;
|
||||
using ThingsGateway.NewLife.Json.Extension;
|
||||
|
||||
namespace ThingsGateway.Gateway.Application;
|
||||
|
||||
@@ -88,7 +89,7 @@ public static class RuleHelpers
|
||||
var propertyInfos = nodeModel.GetType().GetRuntimeProperties().Where(a => a.GetCustomAttribute<ModelValue>() != null);
|
||||
foreach (var item in propertyInfos)
|
||||
{
|
||||
jtokens.Add(item.Name, JToken.FromObject(item.GetValue(nodeModel) ?? JValue.CreateNull()));
|
||||
jtokens.Add(item.Name, (item.GetValue(nodeModel) ?? JValue.CreateNull()).GetJTokenFromObj());
|
||||
}
|
||||
return jtokens;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
using ThingsGateway.NewLife.Json.Extension;
|
||||
|
||||
namespace ThingsGateway.Gateway.Application;
|
||||
|
||||
public class NodeInput
|
||||
@@ -9,7 +11,7 @@ public class NodeInput
|
||||
{
|
||||
get
|
||||
{
|
||||
return JToken.FromObject(input);
|
||||
return (input).GetJTokenFromObj();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
using ThingsGateway.NewLife.Json.Extension;
|
||||
|
||||
namespace ThingsGateway.Gateway.Application;
|
||||
|
||||
public class NodeOutput
|
||||
@@ -9,7 +11,7 @@ public class NodeOutput
|
||||
{
|
||||
get
|
||||
{
|
||||
return JToken.FromObject(output);
|
||||
return (output).GetJTokenFromObj();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ internal static class RuntimeServiceHelper
|
||||
{
|
||||
newDeviceRuntime.Init(newChannelRuntime);
|
||||
|
||||
var newVariableRuntimes = (await GlobalData.VariableService.GetAllAsync(newDeviceRuntime.Id).ConfigureAwait(false)).AdaptListVariableRuntime();
|
||||
var newVariableRuntimes = (await GlobalData.VariableService.GetAllAsync(newDeviceRuntime.Id).ConfigureAwait(false)).AdaptEnumerableVariableRuntime();
|
||||
|
||||
newVariableRuntimes.ParallelForEach(item => item.Init(newDeviceRuntime));
|
||||
}
|
||||
@@ -79,7 +79,7 @@ internal static class RuntimeServiceHelper
|
||||
{
|
||||
newDeviceRuntime.Init(newChannelRuntime);
|
||||
|
||||
var newVariableRuntimes = (await GlobalData.VariableService.GetAllAsync(newDeviceRuntime.Id).ConfigureAwait(false)).AdaptListVariableRuntime();
|
||||
var newVariableRuntimes = (await GlobalData.VariableService.GetAllAsync(newDeviceRuntime.Id).ConfigureAwait(false)).AdaptEnumerableVariableRuntime();
|
||||
|
||||
newVariableRuntimes.ParallelForEach(item => item.Init(newDeviceRuntime));
|
||||
}
|
||||
@@ -113,8 +113,7 @@ internal static class RuntimeServiceHelper
|
||||
}
|
||||
if (deviceRuntime != null)
|
||||
{
|
||||
var list = deviceRuntime.VariableRuntimes.Select(a => a.Value).ToArray();
|
||||
list.ParallelForEach(a => a.Init(newDeviceRuntime));
|
||||
deviceRuntime.VariableRuntimes.ParallelForEach(a => a.Value.Init(newDeviceRuntime));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -141,7 +140,7 @@ internal static class RuntimeServiceHelper
|
||||
{
|
||||
var channels = oldChannelRuntimes.ToArray();
|
||||
var devs = channels.SelectMany(a => a.DeviceRuntimes).Select(a => a.Value).ToArray();
|
||||
devs.SelectMany(a => a.VariableRuntimes).Select(a => a.Value).ToArray().ParallelForEach(a => a.Dispose());
|
||||
devs.SelectMany(a => a.VariableRuntimes).Select(a => a.Value).ParallelForEach(a => a.Dispose());
|
||||
devs.ParallelForEach(a => a.Dispose());
|
||||
channels.ParallelForEach(a => a.Dispose());
|
||||
|
||||
@@ -168,7 +167,7 @@ internal static class RuntimeServiceHelper
|
||||
foreach (var deviceRuntime in deviceRuntimes)
|
||||
{
|
||||
//也需要删除变量
|
||||
var vars = deviceRuntime.VariableRuntimes.Select(a => a.Value).ToArray();
|
||||
var vars = deviceRuntime.VariableRuntimes.Select(a => a.Value);
|
||||
vars.ParallelForEach(v =>
|
||||
{
|
||||
//需要重启业务线程
|
||||
@@ -205,7 +204,7 @@ internal static class RuntimeServiceHelper
|
||||
//也需要删除设备和变量
|
||||
devs.ParallelForEach((a =>
|
||||
{
|
||||
var vars = a.VariableRuntimes.Select(b => b.Value).ToArray();
|
||||
var vars = a.VariableRuntimes.Select(b => b.Value);
|
||||
ParallelExtensions.ParallelForEach(vars, (v =>
|
||||
{
|
||||
//需要重启业务线程
|
||||
@@ -240,6 +239,11 @@ internal static class RuntimeServiceHelper
|
||||
if (group.Key != null)
|
||||
await group.Key.RestartDeviceAsync(group.Value, false).ConfigureAwait(false);
|
||||
}
|
||||
foreach (var group in GlobalData.GetAllVariableBusinessDeviceRuntime().Where(a => a.Driver?.DeviceThreadManage != null).GroupBy(a => a.Driver.DeviceThreadManage))
|
||||
{
|
||||
if (group.Key != null)
|
||||
await group.Key.RestartDeviceAsync(group.ToArray(), false).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
public static async Task RemoveDeviceAsync(HashSet<long> newDeciceIds)
|
||||
{
|
||||
@@ -255,11 +259,18 @@ internal static class RuntimeServiceHelper
|
||||
if (group.Key != null)
|
||||
await group.Key.RemoveDeviceAsync(group.Value.Select(a => a.Id).ToArray()).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
foreach (var group in GlobalData.GetAllVariableBusinessDeviceRuntime().Where(a => a.Driver?.DeviceThreadManage != null).GroupBy(a => a.Driver.DeviceThreadManage))
|
||||
{
|
||||
if (group.Key != null)
|
||||
await group.Key.RestartDeviceAsync(group.ToArray(), false).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static async Task ChangedDriverAsync(ILogger logger)
|
||||
{
|
||||
var channelDevice = GlobalData.IdDevices.Where(a => a.Value.Driver?.DriverProperties is IBusinessPropertyAllVariableBase property && property.IsAllVariable).Select(a => a.Value).ToArray();
|
||||
var channelDevice = GlobalData.GetAllVariableBusinessDeviceRuntime();
|
||||
|
||||
await channelDevice.ParallelForEachAsync(async (item, token) =>
|
||||
{
|
||||
@@ -275,7 +286,7 @@ internal static class RuntimeServiceHelper
|
||||
}
|
||||
public static async Task ChangedDriverAsync(ConcurrentHashSet<IDriver> changedDriver, ILogger logger)
|
||||
{
|
||||
var drivers = GlobalData.IdDevices.Where(a => a.Value.Driver?.DriverProperties is IBusinessPropertyAllVariableBase property && property.IsAllVariable).Select(a => a.Value.Driver);
|
||||
var drivers = GlobalData.GetAllVariableBusinessDriver();
|
||||
|
||||
var changedDrivers = drivers.Concat(changedDriver).Where(a => a.DisposedValue == false).Distinct().ToArray();
|
||||
await changedDrivers.ParallelForEachAsync(async (driver, token) =>
|
||||
@@ -293,7 +304,7 @@ internal static class RuntimeServiceHelper
|
||||
|
||||
public static void AddBusinessChangedDriver(HashSet<long> variableIds, ConcurrentHashSet<IDriver> changedDriver)
|
||||
{
|
||||
var data = GlobalData.IdVariables.FilterByKeys(variableIds).GroupBy(a => a.Value.DeviceRuntime);
|
||||
var data = GlobalData.IdVariables.FilterByKeys(variableIds).GroupBy(a => a.Value.DeviceRuntime).Where(a => a.Key != null);
|
||||
|
||||
foreach (var group in data)
|
||||
{
|
||||
|
||||
@@ -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.25" />
|
||||
<!--<PackageReference Include="TouchSocket.WebApi.Swagger" Version="4.0.0-beta.25" />-->
|
||||
<PackageReference Include="TouchSocket.WebApi" Version="4.0.0-beta.25" />
|
||||
<PackageReference Include="TouchSocket.Dmtp" Version="4.0.0-beta.57" />
|
||||
<!--<PackageReference Include="TouchSocket.WebApi.Swagger" Version="4.0.0-beta.57" />-->
|
||||
<PackageReference Include="TouchSocket.WebApi" Version="4.0.0-beta.57" />
|
||||
<PackageReference Include="ThingsGateway.Authentication" Version="$(AuthenticationVersion)" />
|
||||
<!--<ProjectReference Include="..\..\PluginPro\ThingsGateway.Authentication\ThingsGateway.Authentication.csproj" />-->
|
||||
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
}
|
||||
/*切换高度*/
|
||||
.quickactions-list.is-open {
|
||||
height: 300px;
|
||||
height: 200px;
|
||||
}
|
||||
|
||||
.quickactions-header {
|
||||
|
||||
@@ -167,7 +167,9 @@
|
||||
"Date": "Date"
|
||||
},
|
||||
"ThingsGateway.Gateway.Razor.ChannelDeviceTree": {
|
||||
"ShowType": "ShowType"
|
||||
"ChannelTable": "ChannelTable",
|
||||
"DeviceTable": "DeviceTable",
|
||||
"LogInfo": "LogInfo"
|
||||
},
|
||||
"ThingsGateway.Gateway.Razor.GatewayAbout": {
|
||||
"AuthName": "AuthName",
|
||||
|
||||
@@ -167,7 +167,9 @@
|
||||
"Date": "日期"
|
||||
},
|
||||
"ThingsGateway.Gateway.Razor.ChannelDeviceTree": {
|
||||
"ShowType": "显示类型"
|
||||
"ChannelTable": "通道表格",
|
||||
"DeviceTable": "设备表格",
|
||||
"LogInfo": "日志信息"
|
||||
},
|
||||
"ThingsGateway.Gateway.Razor.GatewayAbout": {
|
||||
"AuthName": "公司名称",
|
||||
|
||||
@@ -13,7 +13,7 @@ namespace ThingsGateway.Gateway.Razor;
|
||||
public partial class ChannelRuntimeInfo
|
||||
{
|
||||
#if !Management
|
||||
private string Height { get; set; } = "calc(100% - 270px)";
|
||||
private string Height { get; set; } = "calc(100% - 300px)";
|
||||
#else
|
||||
private string Height { get; set; } = "calc(100% - 330px)";
|
||||
|
||||
|
||||
@@ -6,12 +6,11 @@
|
||||
<div class="listtree-view">
|
||||
|
||||
<div class="d-flex align-items-center">
|
||||
<RadioList class="m-2" IsButton="true" AutoSelectFirstWhenValueIsNull="false" TValue="ShowTypeEnum?" ValueExpression=@(() => ShowType) Value="ShowType" ValueChanged="OnShowTypeChanged" ShowLabel="false" />
|
||||
<span style="color:var(--bs-body-color);opacity: 0.5;" class="text-h6 py-1">@GatewayLocalizer["DeviceList"]</span>
|
||||
|
||||
<SpinnerComponent @ref=Spinner></SpinnerComponent>
|
||||
<SpinnerComponent Size="Size.Small" @ref=Spinner></SpinnerComponent>
|
||||
</div>
|
||||
|
||||
<span style="color:var(--bs-body-color);opacity: 0.5;" class="text-h6 mb-2">@GatewayLocalizer["DeviceList"]</span>
|
||||
|
||||
<ContextMenuZone>
|
||||
<TreeView TItem="ChannelDeviceTreeItem" Items="Items" ShowIcon="false" ShowSearch IsAccordion=false IsVirtualize="true" OnTreeItemClick="OnTreeItemClick" OnSearchAsync="OnClickSearch" ModelEqualityComparer=ModelEqualityComparer ShowToolbar="true" >
|
||||
@@ -37,7 +36,7 @@
|
||||
@if (context.ChannelDevicePluginType == ChannelDevicePluginTypeEnum.Channel)
|
||||
{
|
||||
@* <Tooltip Title=@(GatewayLocalizer["AddDevice"]) Placement="Placement.Bottom"> *@
|
||||
<Button Size=Size.ExtraSmall Icon="fa-solid fa-plus" Color=Color.None OnClickWithoutRender=@(() => EditDevice(GatewayLocalizer["AddDevice"], context, ItemChangedType.Add))>
|
||||
<Button Size=Size.ExtraSmall Icon="fa-solid fa-circle-info" Color=Color.None OnClickWithoutRender=@(() => ShowLogInfo(context))>
|
||||
</Button>
|
||||
@* </Tooltip> *@
|
||||
|
||||
@@ -58,6 +57,10 @@
|
||||
@* //更新设备 *@
|
||||
@if (context.ChannelDevicePluginType == ChannelDevicePluginTypeEnum.Device)
|
||||
{
|
||||
|
||||
<Button Size=Size.ExtraSmall Icon="fa-solid fa-circle-info" Color=Color.None OnClickWithoutRender=@(() => ShowLogInfo(context))>
|
||||
</Button>
|
||||
|
||||
@* <Tooltip Title=@(GatewayLocalizer["UpdateDevice"]) Placement="Placement.Bottom"> *@
|
||||
<Button Size=Size.ExtraSmall Icon="fa-regular fa-pen-to-square" Color=Color.None OnClickWithoutRender=@(() => EditDevice(GatewayLocalizer["UpdateDevice"], context, ItemChangedType.Update))>
|
||||
</Button>
|
||||
@@ -77,6 +80,14 @@
|
||||
<ContextMenu style="max-height:800px" class="tgTree" OnBeforeShowCallback="OnBeforeShowCallback">
|
||||
@if (SelectModel != null)
|
||||
{
|
||||
<ContextMenuItem Icon="fa-solid fa-table" Text="@Localizer["ChannelTable"]" OnClick=@((a, b) => ShowChannelRuntimeTable(b as ChannelDeviceTreeItem))></ContextMenuItem>
|
||||
<ContextMenuItem Icon="fa-solid fa-table" Text="@Localizer["DeviceTable"]" OnClick=@((a, b) => ShowDeviceRuntimeTable(b as ChannelDeviceTreeItem))></ContextMenuItem>
|
||||
|
||||
|
||||
<ContextMenuItem Icon="fa-solid fa-circle-info" Disabled="SelectModel.ChannelDevicePluginType < ChannelDevicePluginTypeEnum.Channel" Text ="@Localizer["LogInfo"]" OnClick=@((a, b) => ShowLogInfo(b as ChannelDeviceTreeItem))></ContextMenuItem>
|
||||
|
||||
<ContextMenuDivider></ContextMenuDivider>
|
||||
|
||||
<ContextMenuItem Icon="fa-solid fa-plus" Disabled="(SelectModel.ChannelDevicePluginType > ChannelDevicePluginTypeEnum.Channel || !AuthorizeButton(AdminOperConst.Add))" Text="@GatewayLocalizer["AddChannel"]" OnClick=@((a, b) => EditChannel(a, b, ItemChangedType.Add))></ContextMenuItem>
|
||||
|
||||
<ContextMenuItem Icon="fa-regular fa-pen-to-square" Disabled="(SelectModel.ChannelDevicePluginType >= ChannelDevicePluginTypeEnum.Channel || !AuthorizeButton(AdminOperConst.Edit))" Text="@GatewayLocalizer["BatchEditChannel"]" OnClick=@((a, b) => BatchEditChannel(a, b))></ContextMenuItem>
|
||||
|
||||
@@ -10,13 +10,11 @@
|
||||
|
||||
using Microsoft.AspNetCore.Components.Forms;
|
||||
using Microsoft.AspNetCore.Components.Web;
|
||||
using Microsoft.JSInterop;
|
||||
|
||||
using ThingsGateway.Admin.Application;
|
||||
using ThingsGateway.Admin.Razor;
|
||||
using ThingsGateway.NewLife.Extension;
|
||||
using ThingsGateway.NewLife.Json.Extension;
|
||||
using ThingsGateway.Razor.Extension;
|
||||
using ThingsGateway.SqlSugar;
|
||||
|
||||
namespace ThingsGateway.Gateway.Razor;
|
||||
@@ -39,32 +37,258 @@ public partial class ChannelDeviceTree : IDisposable
|
||||
return AppContext.IsHasButtonWithRole(RouteName, operate);
|
||||
}
|
||||
|
||||
[Parameter]
|
||||
public EventCallback<ShowTypeEnum?> ShowTypeChanged { get; set; }
|
||||
[Parameter]
|
||||
public ShowTypeEnum? ShowType { get; set; }
|
||||
[Inject]
|
||||
IJSRuntime JSRuntime { get; set; }
|
||||
private async Task OnShowTypeChanged(ShowTypeEnum? showType)
|
||||
#if !Management
|
||||
private async Task ShowChannelRuntimeTable(ChannelDeviceTreeItem channelDeviceTreeItem)
|
||||
{
|
||||
ShowType = showType;
|
||||
if (showType != null)
|
||||
await JSRuntime.SetLocalStorage("showType", ShowType);
|
||||
if (ShowTypeChanged.HasDelegate)
|
||||
await ShowTypeChanged.InvokeAsync(showType);
|
||||
if (channelDeviceTreeItem.TryGetChannelRuntime(out var channelRuntime))
|
||||
{
|
||||
var ChannelRuntimes = Enumerable.Repeat(channelRuntime, 1);
|
||||
await ShowChannelTable(ChannelRuntimes);
|
||||
|
||||
}
|
||||
else if (channelDeviceTreeItem.TryGetDeviceRuntime(out var deviceRuntime))
|
||||
{
|
||||
|
||||
var ChannelRuntimes = Enumerable.Repeat(deviceRuntime.ChannelRuntime, 1);
|
||||
await ShowChannelTable(ChannelRuntimes);
|
||||
|
||||
}
|
||||
else if (channelDeviceTreeItem.TryGetPluginName(out var pluginName))
|
||||
{
|
||||
|
||||
var channels = await GlobalData.GetCurrentUserChannels().ConfigureAwait(false);
|
||||
var ChannelRuntimes = channels.Where(a => a.PluginName == pluginName);
|
||||
await ShowChannelTable(ChannelRuntimes);
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
var channels = await GlobalData.GetCurrentUserChannels().ConfigureAwait(false);
|
||||
|
||||
if (channelDeviceTreeItem.TryGetPluginType(out var pluginTypeEnum))
|
||||
{
|
||||
if (pluginTypeEnum != null)
|
||||
{
|
||||
var ChannelRuntimes = channels.Where(a => a.PluginType == pluginTypeEnum);
|
||||
await ShowChannelTable(ChannelRuntimes);
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
var ChannelRuntimes = channels;
|
||||
await ShowChannelTable(ChannelRuntimes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||
|
||||
private async Task ShowDeviceRuntimeTable(ChannelDeviceTreeItem channelDeviceTreeItem)
|
||||
{
|
||||
if (firstRender)
|
||||
|
||||
|
||||
if (channelDeviceTreeItem.TryGetChannelRuntime(out var channelRuntime))
|
||||
{
|
||||
var showType = await JSRuntime!.GetLocalStorage<ShowTypeEnum>("showType");
|
||||
await OnShowTypeChanged(showType);
|
||||
StateHasChanged();
|
||||
|
||||
var DeviceRuntimes = channelRuntime.ReadDeviceRuntimes.Select(a => a.Value);
|
||||
|
||||
await ShowDeviceTable(DeviceRuntimes);
|
||||
|
||||
}
|
||||
await base.OnAfterRenderAsync(firstRender);
|
||||
else if (channelDeviceTreeItem.TryGetDeviceRuntime(out var deviceRuntime))
|
||||
{
|
||||
|
||||
var DeviceRuntimes = Enumerable.Repeat(deviceRuntime, 1);
|
||||
await ShowDeviceTable(DeviceRuntimes);
|
||||
|
||||
|
||||
}
|
||||
else if (channelDeviceTreeItem.TryGetPluginName(out var pluginName))
|
||||
{
|
||||
var devices = await GlobalData.GetCurrentUserDevices().ConfigureAwait(false);
|
||||
|
||||
var DeviceRuntimes = devices.Where(a => a.PluginName == pluginName);
|
||||
await ShowDeviceTable(DeviceRuntimes);
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
if (channelDeviceTreeItem.TryGetPluginType(out var pluginTypeEnum))
|
||||
{
|
||||
if (pluginTypeEnum != null)
|
||||
{
|
||||
var devices = await GlobalData.GetCurrentUserDevices().ConfigureAwait(false);
|
||||
|
||||
var DeviceRuntimes = devices.Where(a => a.PluginType == pluginTypeEnum);
|
||||
await ShowDeviceTable(DeviceRuntimes);
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
var devices = await GlobalData.GetCurrentUserDevices().ConfigureAwait(false);
|
||||
var DeviceRuntimes = devices;
|
||||
await ShowDeviceTable(DeviceRuntimes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
private async Task ShowLogInfo(ChannelDeviceTreeItem channelDeviceTreeItem)
|
||||
{
|
||||
|
||||
if (channelDeviceTreeItem.TryGetChannelRuntime(out var channelRuntime))
|
||||
{
|
||||
await ShowLogInfo(channelRuntime);
|
||||
}
|
||||
else if (channelDeviceTreeItem.TryGetDeviceRuntime(out var deviceRuntime))
|
||||
{
|
||||
|
||||
await ShowLogInfo(deviceRuntime);
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
private async Task ShowLogInfo(ChannelRuntime channel)
|
||||
{
|
||||
|
||||
var renderFragment = BootstrapDynamicComponent.CreateComponent(typeof(ChannelRuntimeInfo), new Dictionary<string, object?>()
|
||||
{
|
||||
{nameof(ChannelRuntimeInfo.ChannelRuntime),channel},
|
||||
}).Render();
|
||||
if (renderFragment != null)
|
||||
{
|
||||
var option = new WinBoxOption()
|
||||
{
|
||||
Title = Localizer["LogInfo"],
|
||||
ContentTemplate = renderFragment,
|
||||
Max = false,
|
||||
Width = "60%",
|
||||
Height = "80%",
|
||||
Top = "0%",
|
||||
Left = "10%",
|
||||
Background = "var(--bb-primary-color)",
|
||||
Overflow = true
|
||||
};
|
||||
await WinBoxService.Show(option);
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
private async Task ShowLogInfo(DeviceRuntime device)
|
||||
{
|
||||
var renderFragment = BootstrapDynamicComponent.CreateComponent(typeof(DeviceRuntimeInfo), new Dictionary<string, object?>()
|
||||
{
|
||||
{nameof(DeviceRuntimeInfo.DeviceRuntime),device},
|
||||
}).Render();
|
||||
if (renderFragment != null)
|
||||
{
|
||||
var option = new WinBoxOption()
|
||||
{
|
||||
Title = Localizer["LogInfo"],
|
||||
ContentTemplate = renderFragment,
|
||||
Max = false,
|
||||
Width = "60%",
|
||||
Height = "80%",
|
||||
Top = "0%",
|
||||
Left = "10%",
|
||||
Background = "var(--bb-primary-color)",
|
||||
Overflow = true
|
||||
};
|
||||
await WinBoxService.Show(option);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
private async Task ShowChannelTable(IEnumerable<ChannelRuntime> ChannelRuntimes)
|
||||
{
|
||||
var renderFragment = BootstrapDynamicComponent.CreateComponent(typeof(ChannelTable), new Dictionary<string, object?>()
|
||||
{
|
||||
{nameof(ChannelTable.SelectModel),SelectModel},
|
||||
{nameof(ChannelTable.Items),ChannelRuntimes},
|
||||
{nameof(ChannelTable.AutoRestartThread),AutoRestartThread},
|
||||
}).Render();
|
||||
if (renderFragment != null)
|
||||
{
|
||||
var option = new WinBoxOption()
|
||||
{
|
||||
Title = Localizer["ChannelTable"],
|
||||
ContentTemplate = renderFragment,
|
||||
Max = false,
|
||||
Width = "60%",
|
||||
Height = "60%",
|
||||
Top = "0%",
|
||||
Left = "10%",
|
||||
Background = "var(--bb-primary-color)",
|
||||
Overflow = true
|
||||
};
|
||||
await WinBoxService.Show(option);
|
||||
}
|
||||
}
|
||||
private async Task ShowDeviceTable(IEnumerable<DeviceRuntime> DeviceRuntimes)
|
||||
{
|
||||
var renderFragment = BootstrapDynamicComponent.CreateComponent(typeof(DeviceTable), new Dictionary<string, object?>()
|
||||
{
|
||||
{nameof(DeviceTable.SelectModel),SelectModel},
|
||||
{nameof(DeviceTable.Items),DeviceRuntimes},
|
||||
{nameof(DeviceTable.AutoRestartThread),AutoRestartThread},
|
||||
}).Render();
|
||||
if (renderFragment != null)
|
||||
{
|
||||
var option = new WinBoxOption()
|
||||
{
|
||||
Title = Localizer["DeviceTable"],
|
||||
ContentTemplate = renderFragment,
|
||||
Max = false,
|
||||
Width = "60%",
|
||||
Height = "60%",
|
||||
Top = "0%",
|
||||
Left = "10%",
|
||||
Background = "var(--bb-primary-color)",
|
||||
Overflow = true
|
||||
};
|
||||
await WinBoxService.Show(option);
|
||||
}
|
||||
}
|
||||
|
||||
[Inject]
|
||||
WinBoxService WinBoxService { get; set; }
|
||||
#endif
|
||||
|
||||
//[Parameter]
|
||||
//public EventCallback<ShowTypeEnum?> ShowTypeChanged { get; set; }
|
||||
//[Parameter]
|
||||
//public ShowTypeEnum? ShowType { get; set; }
|
||||
//[Inject]
|
||||
//IJSRuntime JSRuntime { get; set; }
|
||||
//private async Task OnShowTypeChanged(ShowTypeEnum? showType)
|
||||
//{
|
||||
// ShowType = showType;
|
||||
// if (showType != null)
|
||||
// await JSRuntime.SetLocalStorage("showType", ShowType);
|
||||
// if (ShowTypeChanged.HasDelegate)
|
||||
// await ShowTypeChanged.InvokeAsync(showType);
|
||||
//}
|
||||
|
||||
//protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||
//{
|
||||
// if (firstRender)
|
||||
// {
|
||||
// var showType = await JSRuntime!.GetLocalStorage<ShowTypeEnum>("showType");
|
||||
// await OnShowTypeChanged(showType);
|
||||
// StateHasChanged();
|
||||
// }
|
||||
// await base.OnAfterRenderAsync(firstRender);
|
||||
//}
|
||||
|
||||
|
||||
[Parameter]
|
||||
public bool AutoRestartThread { get; set; }
|
||||
@@ -99,8 +323,6 @@ public partial class ChannelDeviceTree : IDisposable
|
||||
[Inject]
|
||||
DialogService DialogService { get; set; }
|
||||
|
||||
[Inject]
|
||||
WinBoxService WinBoxService { get; set; }
|
||||
|
||||
[Inject]
|
||||
[NotNull]
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
<DeviceRuntimeInfo1 DeviceRuntime="DeviceRuntime" />
|
||||
|
||||
<LogConsole HeightString="calc(100% - 270px)" LogLevel=@(LogLevel)
|
||||
<LogConsole HeightString=@Height LogLevel=@(LogLevel)
|
||||
LogLevelChanged="async (logLevel)=>
|
||||
{
|
||||
LogLevel = logLevel;
|
||||
|
||||
@@ -12,6 +12,13 @@ namespace ThingsGateway.Gateway.Razor;
|
||||
|
||||
public partial class DeviceRuntimeInfo
|
||||
{
|
||||
|
||||
#if !Management
|
||||
private string Height { get; set; } = "calc(100% - 300px)";
|
||||
#else
|
||||
private string Height { get; set; } = "calc(100% - 330px)";
|
||||
#endif
|
||||
|
||||
[Parameter, EditorRequired]
|
||||
public DeviceRuntime DeviceRuntime { get; set; }
|
||||
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
@inherits ComponentDefault
|
||||
@using ThingsGateway.Admin.Application
|
||||
@using ThingsGateway.Admin.Razor
|
||||
@using ThingsGateway.Gateway.Application
|
||||
@namespace ThingsGateway.Gateway.Razor
|
||||
|
||||
|
||||
@if (ShowType == ShowTypeEnum.VariableTable)
|
||||
{
|
||||
<VariableRuntimeInfo Items="VariableRuntimes" SelectModel="SelectModel" AutoRestartThread="AutoRestartThread" />
|
||||
}
|
||||
else if (ShowType == ShowTypeEnum.LogInfo)
|
||||
{
|
||||
if (GlobalData.ReadOnlyIdDevices.TryGetValue(ShowDeviceRuntime, out var device))
|
||||
{
|
||||
<DeviceRuntimeInfo DeviceRuntime="device" />
|
||||
}
|
||||
if (GlobalData.ReadOnlyIdChannels.TryGetValue(ShowChannelRuntime, out var channel))
|
||||
{
|
||||
<ChannelRuntimeInfo ChannelRuntime="channel" />
|
||||
}
|
||||
}
|
||||
else if (ShowType == ShowTypeEnum.ChannelTable)
|
||||
{
|
||||
<ChannelTable SelectModel="SelectModel" Items="ChannelRuntimes" AutoRestartThread=AutoRestartThread />
|
||||
}
|
||||
|
||||
else if (ShowType == ShowTypeEnum.DeviceTable)
|
||||
{
|
||||
<DeviceTable SelectModel="SelectModel" Items="DeviceRuntimes" AutoRestartThread=AutoRestartThread />
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace ThingsGateway.Gateway.Razor;
|
||||
|
||||
public partial class GatewayInfo : IDisposable
|
||||
{
|
||||
[Parameter]
|
||||
public ChannelDeviceTreeItem SelectModel { get; set; } = new() { ChannelDevicePluginType = ChannelDevicePluginTypeEnum.PluginType, PluginType = null };
|
||||
|
||||
[Parameter]
|
||||
public IEnumerable<VariableRuntime> VariableRuntimes { get; set; } = Enumerable.Empty<VariableRuntime>();
|
||||
|
||||
[Parameter]
|
||||
public IEnumerable<ChannelRuntime> ChannelRuntimes { get; set; } = Enumerable.Empty<ChannelRuntime>();
|
||||
|
||||
[Parameter]
|
||||
public IEnumerable<DeviceRuntime> DeviceRuntimes { get; set; } = Enumerable.Empty<DeviceRuntime>();
|
||||
[Parameter]
|
||||
public long ShowChannelRuntime { get; set; }
|
||||
[Parameter]
|
||||
public long ShowDeviceRuntime { get; set; }
|
||||
[Parameter]
|
||||
public ShowTypeEnum? ShowType { get; set; }
|
||||
[Parameter]
|
||||
public bool AutoRestartThread { get; set; } = true;
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
_ = RunTimerAsync();
|
||||
base.OnInitialized();
|
||||
}
|
||||
|
||||
private bool Disposed;
|
||||
private async Task RunTimerAsync()
|
||||
{
|
||||
while (!Disposed)
|
||||
{
|
||||
try
|
||||
{
|
||||
await InvokeAsync(StateHasChanged);
|
||||
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
NewLife.Log.XTrace.WriteException(ex);
|
||||
}
|
||||
finally
|
||||
{
|
||||
await Task.Delay(5000);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Disposed = true;
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,7 @@
|
||||
|
||||
<Card IsShadow=true class="h-100 me-1" Color="Color.Primary">
|
||||
<BodyTemplate>
|
||||
<ChannelDeviceTree @bind-ShowType=ShowType AutoRestartThread="AutoRestartThread"
|
||||
<ChannelDeviceTree AutoRestartThread="AutoRestartThread"
|
||||
ChannelDeviceChanged="TreeChangedAsync" Value="SelectModel"></ChannelDeviceTree>
|
||||
</BodyTemplate>
|
||||
</Card>
|
||||
@@ -24,7 +24,7 @@
|
||||
</FirstPaneTemplate>
|
||||
<SecondPaneTemplate>
|
||||
|
||||
<GatewayInfo AutoRestartThread=AutoRestartThread SelectModel=SelectModel ShowChannelRuntime=ShowChannelRuntime ShowDeviceRuntime=ShowDeviceRuntime ShowType=ShowType VariableRuntimes=VariableRuntimes ChannelRuntimes="ChannelRuntimes" DeviceRuntimes="DeviceRuntimes" />
|
||||
<VariableRuntimeInfo Items="VariableRuntimes" SelectModel="SelectModel" AutoRestartThread="AutoRestartThread" />
|
||||
|
||||
</SecondPaneTemplate>
|
||||
</Split>
|
||||
|
||||
@@ -18,15 +18,15 @@ public partial class GatewayMonitorPage
|
||||
|
||||
private async Task TreeChangedAsync(ChannelDeviceTreeItem channelDeviceTreeItem)
|
||||
{
|
||||
ShowChannelRuntime = 0;
|
||||
ShowDeviceRuntime = 0;
|
||||
//ShowChannelRuntime = 0;
|
||||
//ShowDeviceRuntime = 0;
|
||||
SelectModel = channelDeviceTreeItem;
|
||||
var variables = await GlobalData.GetCurrentUserIdVariables().ConfigureAwait(false);
|
||||
var channels = await GlobalData.GetCurrentUserChannels().ConfigureAwait(false);
|
||||
var devices = await GlobalData.GetCurrentUserDevices().ConfigureAwait(false);
|
||||
if (channelDeviceTreeItem.TryGetChannelRuntime(out var channelRuntime))
|
||||
{
|
||||
ShowChannelRuntime = channelRuntime.Id;
|
||||
//ShowChannelRuntime = channelRuntime.Id;
|
||||
|
||||
if (channelRuntime.IsCollect == true)
|
||||
{
|
||||
@@ -36,23 +36,37 @@ public partial class GatewayMonitorPage
|
||||
{
|
||||
VariableRuntimes = channelRuntime.ReadDeviceRuntimes.Where(a => a.Value?.Driver?.IdVariableRuntimes != null).SelectMany(a => a.Value?.Driver?.IdVariableRuntimes?.Where(a => a.Value != null)?.Select(a => a.Value)).Where(a => a != null);
|
||||
}
|
||||
ChannelRuntimes = Enumerable.Repeat(channelRuntime, 1);
|
||||
DeviceRuntimes = channelRuntime.ReadDeviceRuntimes.Select(a => a.Value);
|
||||
//ChannelRuntimes = Enumerable.Repeat(channelRuntime, 1);
|
||||
//DeviceRuntimes = channelRuntime.ReadDeviceRuntimes.Select(a => a.Value);
|
||||
}
|
||||
else if (channelDeviceTreeItem.TryGetDeviceRuntime(out var deviceRuntime))
|
||||
{
|
||||
ShowDeviceRuntime = deviceRuntime.Id;
|
||||
//ShowDeviceRuntime = deviceRuntime.Id;
|
||||
if (deviceRuntime.IsCollect == true)
|
||||
{
|
||||
VariableRuntimes = deviceRuntime.ReadOnlyVariableRuntimes.Select(a => a.Value).Where(a => a != null);
|
||||
}
|
||||
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);
|
||||
//ChannelRuntimes = Enumerable.Repeat(deviceRuntime.ChannelRuntime, 1);
|
||||
//DeviceRuntimes = Enumerable.Repeat(deviceRuntime, 1);
|
||||
}
|
||||
else if (channelDeviceTreeItem.TryGetPluginName(out var pluginName))
|
||||
{
|
||||
@@ -66,26 +80,26 @@ public partial class GatewayMonitorPage
|
||||
VariableRuntimes = channels.Where(a => a.PluginName == pluginName).SelectMany(a => a.ReadDeviceRuntimes).Where(a => a.Value.Driver?.IdVariableRuntimes != null).SelectMany(a => a.Value.Driver?.IdVariableRuntimes).Select(a => a.Value);
|
||||
}
|
||||
|
||||
ChannelRuntimes = channels.Where(a => a.PluginName == pluginName);
|
||||
DeviceRuntimes = devices.Where(a => a.PluginName == pluginName);
|
||||
//ChannelRuntimes = channels.Where(a => a.PluginName == pluginName);
|
||||
//DeviceRuntimes = devices.Where(a => a.PluginName == pluginName);
|
||||
}
|
||||
else
|
||||
{
|
||||
VariableRuntimes = variables.Where(a => a != null);
|
||||
|
||||
if (channelDeviceTreeItem.TryGetPluginType(out var pluginTypeEnum))
|
||||
{
|
||||
if (pluginTypeEnum != null)
|
||||
{
|
||||
ChannelRuntimes = channels.Where(a => a.PluginType == pluginTypeEnum);
|
||||
DeviceRuntimes = devices.Where(a => a.PluginType == pluginTypeEnum);
|
||||
}
|
||||
else
|
||||
{
|
||||
ChannelRuntimes = channels;
|
||||
DeviceRuntimes = devices;
|
||||
}
|
||||
}
|
||||
//if (channelDeviceTreeItem.TryGetPluginType(out var pluginTypeEnum))
|
||||
//{
|
||||
// if (pluginTypeEnum != null)
|
||||
// {
|
||||
// ChannelRuntimes = channels.Where(a => a.PluginType == pluginTypeEnum);
|
||||
// DeviceRuntimes = devices.Where(a => a.PluginType == pluginTypeEnum);
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// ChannelRuntimes = channels;
|
||||
// DeviceRuntimes = devices;
|
||||
// }
|
||||
//}
|
||||
}
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
@@ -99,11 +113,11 @@ public partial class GatewayMonitorPage
|
||||
}
|
||||
public IEnumerable<VariableRuntime> VariableRuntimes { get; set; } = Enumerable.Empty<VariableRuntime>();
|
||||
|
||||
public IEnumerable<ChannelRuntime> ChannelRuntimes { get; set; } = Enumerable.Empty<ChannelRuntime>();
|
||||
public IEnumerable<DeviceRuntime> DeviceRuntimes { get; set; } = Enumerable.Empty<DeviceRuntime>();
|
||||
//public IEnumerable<ChannelRuntime> ChannelRuntimes { get; set; } = Enumerable.Empty<ChannelRuntime>();
|
||||
//public IEnumerable<DeviceRuntime> DeviceRuntimes { get; set; } = Enumerable.Empty<DeviceRuntime>();
|
||||
|
||||
private long ShowChannelRuntime { get; set; }
|
||||
private long ShowDeviceRuntime { get; set; }
|
||||
public ShowTypeEnum? ShowType { get; set; }
|
||||
//private long ShowChannelRuntime { get; set; }
|
||||
//private long ShowDeviceRuntime { get; set; }
|
||||
//public ShowTypeEnum? ShowType { get; set; }
|
||||
private bool AutoRestartThread { get; set; } = true;
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
LogLevel=a;
|
||||
if(LogLevelChanged!=null)
|
||||
{
|
||||
await LogLevelChanged?.Invoke(a);
|
||||
await LogLevelChanged.Invoke(a);
|
||||
}
|
||||
}" LogPath=@LogPath HeaderText=@HeaderText></ThingsGateway.Debug.LogConsole>
|
||||
|
||||
|
||||
@@ -47,7 +47,7 @@ public partial class RedundancyOptionsPage
|
||||
LogLevel = logLevel;
|
||||
if (LogLevelChanged != null)
|
||||
{
|
||||
await LogLevelChanged?.Invoke(LogLevel);
|
||||
await LogLevelChanged.Invoke(LogLevel);
|
||||
}
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
|
||||
@@ -15,8 +15,8 @@ public partial class DragAndDrop
|
||||
{
|
||||
private readonly BlazorDiagram _blazorDiagram = new(new BlazorDiagramOptions
|
||||
{
|
||||
GridSize = 75,
|
||||
GridSnapToCenter = true,
|
||||
GridSize = null,
|
||||
GridSnapToCenter = false,
|
||||
});
|
||||
private string? _draggedType;
|
||||
[Inject]
|
||||
|
||||
@@ -77,7 +77,7 @@ ShowDesign=true;}" />
|
||||
</RowButtonTemplate>
|
||||
</AdminTable>
|
||||
</div>
|
||||
<div class="col-12 col-sm-6 h-100">
|
||||
<div class="col-12 col-sm-6 h-100 ps-2">
|
||||
|
||||
<RulesStatus RulesId="RulesId"></RulesStatus>
|
||||
</div>
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
|
||||
@if (_rules != null)
|
||||
{
|
||||
<LogConsole HeightString="100%" LogLevel=@(LogLevel) LogLevelChanged="(a)=>{
|
||||
<LogConsole HeightString="calc(100% - 50px)" LogLevel=@(LogLevel) LogLevelChanged="(a)=>{
|
||||
LogLevel=a;
|
||||
return RulesEngineHostedService.SetRulesLogLevelAsync(RulesId, a);
|
||||
}" LogPath=@LogPath HeaderText="@_rules.Name"></LogConsole>
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
59
src/Plugin/ThingsGateway.Foundation.Benchmark/Program.cs
Normal file
59
src/Plugin/ThingsGateway.Foundation.Benchmark/Program.cs
Normal 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 = 3;
|
||||
public static int TaskNumberOfItems = 1;
|
||||
public static int NumberOfItems = 3;
|
||||
|
||||
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)
|
||||
//);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user