mirror of
https://gitee.com/ThingsGateway/ThingsGateway.git
synced 2025-11-05 10:03:58 +08:00
Compare commits
6 Commits
variablePa
...
00c24d06a3
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
00c24d06a3 | ||
|
|
3461f34240 | ||
|
|
aa1ce08c02 | ||
|
|
9c230c2da9 | ||
|
|
21215d0379 | ||
|
|
7448183791 |
@@ -4,8 +4,8 @@
|
|||||||
|
|
||||||
<div class="tg-table h-100">
|
<div class="tg-table h-100">
|
||||||
|
|
||||||
<Table TItem="TItem" IsBordered="true" IsStriped="true" TableSize="TableSize.Compact" SelectedRows=SelectedRows SelectedRowsChanged=privateSelectedRowsChanged IsMultipleSelect="IsMultipleSelect" @ref="Instance" SearchTemplate="SearchTemplate"
|
<Table Id=@Id TItem="TItem" IsBordered="true" IsStriped="true" TableSize="TableSize.Compact" SelectedRows=SelectedRows SelectedRowsChanged=privateSelectedRowsChanged IsMultipleSelect="IsMultipleSelect" @ref="Instance" SearchTemplate="SearchTemplate"
|
||||||
DataService="DataService" CreateItemCallback="CreateItemCallback!" RenderMode=RenderMode
|
DataService="DataService" CreateItemCallback="CreateItemCallback!" RenderMode=RenderMode OnColumnCreating=OnColumnCreating
|
||||||
IsPagination="IsPagination" PageItemsSource="PageItemsSource" IsFixedHeader="IsFixedHeader" IndentSize=24 RowHeight=RowHeight ShowSearchText="ShowSearchText" ShowSearchButton="ShowSearchButton" DisableEditButtonCallback="DisableEditButtonCallback" DisableDeleteButtonCallback="DisableDeleteButtonCallback" 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
|
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
|
ShowDefaultButtons="ShowDefaultButtons" ShowAdvancedSearch="ShowAdvancedSearch" ShowResetButton=ShowResetButton
|
||||||
@@ -14,7 +14,7 @@
|
|||||||
ShowSkeleton="true" ShowLoading="ShowLoading" ShowSearch="ShowSearch" SearchModel=@SearchModel ShowLineNo
|
ShowSkeleton="true" ShowLoading="ShowLoading" ShowSearch="ShowSearch" SearchModel=@SearchModel ShowLineNo
|
||||||
SearchMode=SearchMode ShowExportPdfButton=ShowExportPdfButton ExportButtonText=@ExportButtonText
|
SearchMode=SearchMode ShowExportPdfButton=ShowExportPdfButton ExportButtonText=@ExportButtonText
|
||||||
ShowExportButton=@ShowExportButton Items=Items ClickToSelect=ClickToSelect ScrollMode=ScrollMode
|
ShowExportButton=@ShowExportButton Items=Items ClickToSelect=ClickToSelect ScrollMode=ScrollMode
|
||||||
ShowExportCsvButton=@ShowExportCsvButton ShowCardView=ShowCardView
|
ShowExportCsvButton=@ShowExportCsvButton ShowCardView=ShowCardView OnColumnVisibleChanged=OnColumnVisibleChanged
|
||||||
FixedExtendButtonsColumn=FixedExtendButtonsColumn FixedMultipleColumn=FixedMultipleColumn FixedDetailRowHeaderColumn=FixedDetailRowHeaderColumn FixedLineNoColumn=FixedLineNoColumn
|
FixedExtendButtonsColumn=FixedExtendButtonsColumn FixedMultipleColumn=FixedMultipleColumn FixedDetailRowHeaderColumn=FixedDetailRowHeaderColumn FixedLineNoColumn=FixedLineNoColumn
|
||||||
IsAutoRefresh=IsAutoRefresh AutoRefreshInterval=AutoRefreshInterval
|
IsAutoRefresh=IsAutoRefresh AutoRefreshInterval=AutoRefreshInterval
|
||||||
AllowDragColumn=@AllowDragColumn Height=@Height ShowRefresh=ShowRefresh
|
AllowDragColumn=@AllowDragColumn Height=@Height ShowRefresh=ShowRefresh
|
||||||
|
|||||||
@@ -13,13 +13,22 @@ namespace ThingsGateway.Admin.Razor;
|
|||||||
[CascadingTypeParameter(nameof(TItem))]
|
[CascadingTypeParameter(nameof(TItem))]
|
||||||
public partial class AdminTable<TItem> where TItem : class, new()
|
public partial class AdminTable<TItem> where TItem : class, new()
|
||||||
{
|
{
|
||||||
|
/// <inheritdoc cref="Table{TItem}.OnColumnVisibleChanged"/>
|
||||||
|
[Parameter]
|
||||||
|
public Func<string,bool, Task> OnColumnVisibleChanged { get; set; }
|
||||||
|
|
||||||
|
/// <inheritdoc cref="Table{TItem}.OnColumnCreating"/>
|
||||||
|
[Parameter]
|
||||||
|
public Func<List<ITableColumn>,Task> OnColumnCreating { get; set; }
|
||||||
/// <inheritdoc cref="Table{TItem}.RenderMode"/>
|
/// <inheritdoc cref="Table{TItem}.RenderMode"/>
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public TableRenderMode RenderMode { get; set; }
|
public TableRenderMode RenderMode { get; set; }
|
||||||
|
|
||||||
public List<ITableColumn> Columns => Instance?.Columns;
|
public List<ITableColumn> Columns => Instance?.Columns;
|
||||||
|
|
||||||
|
public IEnumerable<ITableColumn> GetVisibleColumns => Instance?.GetVisibleColumns();
|
||||||
|
public List<TItem> Rows => Instance?.Rows;
|
||||||
|
|
||||||
|
|
||||||
/// <inheritdoc cref="Table{TItem}.SelectedRowsChanged"/>
|
/// <inheritdoc cref="Table{TItem}.SelectedRowsChanged"/>
|
||||||
[Parameter]
|
[Parameter]
|
||||||
@@ -158,6 +167,9 @@ public partial class AdminTable<TItem> where TItem : class, new()
|
|||||||
[Parameter]
|
[Parameter]
|
||||||
public IDataService<TItem> DataService { get; set; }
|
public IDataService<TItem> DataService { get; set; }
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public string? Id { get; set; }
|
||||||
|
|
||||||
/// <inheritdoc cref="Table{TItem}.CreateItemCallback"/>
|
/// <inheritdoc cref="Table{TItem}.CreateItemCallback"/>
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public Func<TItem> CreateItemCallback { get; set; }
|
public Func<TItem> CreateItemCallback { get; set; }
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ public partial class HardwareInfoPage : IDisposable
|
|||||||
ChartDataSource.Options.Title = Localizer[nameof(HistoryHardwareInfo)];
|
ChartDataSource.Options.Title = Localizer[nameof(HistoryHardwareInfo)];
|
||||||
ChartDataSource.Options.X.Title = Localizer["DateTime"];
|
ChartDataSource.Options.X.Title = Localizer["DateTime"];
|
||||||
ChartDataSource.Options.Y.Title = Localizer["Data"];
|
ChartDataSource.Options.Y.Title = Localizer["Data"];
|
||||||
ChartDataSource.Labels = hisHardwareInfos.Select(a => a.Date.ToString("dd HH:mm zz"));
|
ChartDataSource.Labels = hisHardwareInfos.Select(a => a.Date.ToString("dd-HH:mm"));
|
||||||
ChartDataSource.Data.Add(new ChartDataset()
|
ChartDataSource.Data.Add(new ChartDataset()
|
||||||
{
|
{
|
||||||
Tension = 0.4f,
|
Tension = 0.4f,
|
||||||
@@ -116,7 +116,7 @@ public partial class HardwareInfoPage : IDisposable
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
var hisHardwareInfos = await HardwareJob.GetHistoryHardwareInfos();
|
var hisHardwareInfos = await HardwareJob.GetHistoryHardwareInfos();
|
||||||
ChartDataSource.Labels = hisHardwareInfos.Select(a => a.Date.ToString("dd HH:mm zz"));
|
ChartDataSource.Labels = hisHardwareInfos.Select(a => a.Date.ToString("dd-HH:mm"));
|
||||||
ChartDataSource.Data[0].Data = hisHardwareInfos.Select(a => (object)a.CpuUsage);
|
ChartDataSource.Data[0].Data = hisHardwareInfos.Select(a => (object)a.CpuUsage);
|
||||||
ChartDataSource.Data[1].Data = hisHardwareInfos.Select(a => (object)a.MemoryUsage);
|
ChartDataSource.Data[1].Data = hisHardwareInfos.Select(a => (object)a.MemoryUsage);
|
||||||
ChartDataSource.Data[2].Data = hisHardwareInfos.Select(a => (object)a.DriveUsage);
|
ChartDataSource.Data[2].Data = hisHardwareInfos.Select(a => (object)a.DriveUsage);
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\ThingsGateway.Admin.Application\ThingsGateway.Admin.Application.csproj" />
|
<ProjectReference Include="..\ThingsGateway.Admin.Application\ThingsGateway.Admin.Application.csproj" />
|
||||||
<PackageReference Include="BootstrapBlazor.Chart" Version="9.0.3" />
|
<PackageReference Include="BootstrapBlazor.Chart" Version="9.0.4" />
|
||||||
<PackageReference Include="BootstrapBlazor.UniverSheet" Version="9.0.5" />
|
<PackageReference Include="BootstrapBlazor.UniverSheet" Version="9.0.5" />
|
||||||
<PackageReference Include="BootstrapBlazor.WinBox" Version="9.0.7" />
|
<PackageReference Include="BootstrapBlazor.WinBox" Version="9.0.7" />
|
||||||
<PackageReference Include="BootstrapBlazor.CodeEditor" Version="9.0.3" />
|
<PackageReference Include="BootstrapBlazor.CodeEditor" Version="9.0.3" />
|
||||||
|
|||||||
@@ -234,7 +234,7 @@ public class TextFileLog : Logger, IDisposable
|
|||||||
if (!_isFile && Backups > 0)
|
if (!_isFile && Backups > 0)
|
||||||
{
|
{
|
||||||
// 判断日志目录是否已存在
|
// 判断日志目录是否已存在
|
||||||
var di = LogPath.GetBasePath().AsDirectory();
|
DirectoryInfo? di = new DirectoryInfo(LogPath);
|
||||||
if (di.Exists)
|
if (di.Exists)
|
||||||
{
|
{
|
||||||
// 删除*.del
|
// 删除*.del
|
||||||
|
|||||||
@@ -318,7 +318,9 @@ public class TimerScheduler : IDisposable, ILogFeature
|
|||||||
|
|
||||||
timer.hasSetNext = false;
|
timer.hasSetNext = false;
|
||||||
|
|
||||||
using var span = timer.Tracer?.NewSpan(timer.TracerName ?? $"timer:Execute", timer.Timers + "");
|
string tracerName = timer.TracerName ?? "timer:ExecuteAsync";
|
||||||
|
string timerArg = timer.Timers.ToString();
|
||||||
|
using var span = timer.Tracer?.NewSpan(tracerName, timerArg);
|
||||||
var sw = _stopwatchPool.Get();
|
var sw = _stopwatchPool.Get();
|
||||||
sw.Restart();
|
sw.Restart();
|
||||||
try
|
try
|
||||||
@@ -331,9 +333,12 @@ public class TimerScheduler : IDisposable, ILogFeature
|
|||||||
timer.Dispose();
|
timer.Dispose();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (timer.TimerCallbackCachedDelegate == null)
|
||||||
var func = timer.Method.As<TimerCallback>(target);
|
{
|
||||||
func!(timer.State);
|
timer.TimerCallbackCachedDelegate = timer.Method.As<TimerCallback>(target);
|
||||||
|
}
|
||||||
|
//var func = timer.Method.As<TimerCallback>(target);
|
||||||
|
timer.TimerCallbackCachedDelegate!(timer.State);
|
||||||
}
|
}
|
||||||
catch (ThreadAbortException) { throw; }
|
catch (ThreadAbortException) { throw; }
|
||||||
catch (ThreadInterruptedException) { throw; }
|
catch (ThreadInterruptedException) { throw; }
|
||||||
@@ -367,7 +372,9 @@ public class TimerScheduler : IDisposable, ILogFeature
|
|||||||
|
|
||||||
timer.hasSetNext = false;
|
timer.hasSetNext = false;
|
||||||
|
|
||||||
using var span = timer.Tracer?.NewSpan(timer.TracerName ?? $"timer:ExecuteAsync", timer.Timers + "");
|
string tracerName = timer.TracerName ?? "timer:ExecuteAsync";
|
||||||
|
string timerArg = timer.Timers.ToString();
|
||||||
|
using var span = timer.Tracer?.NewSpan(tracerName, timerArg);
|
||||||
var sw = _stopwatchPool.Get();
|
var sw = _stopwatchPool.Get();
|
||||||
sw.Restart();
|
sw.Restart();
|
||||||
try
|
try
|
||||||
@@ -381,19 +388,31 @@ public class TimerScheduler : IDisposable, ILogFeature
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#if NET6_0_OR_GREATER
|
#if NET6_0_OR_GREATER
|
||||||
if (timer.IsValueTask)
|
if (timer.IsValueTask)
|
||||||
{
|
{
|
||||||
var func = timer.Method.As<Func<Object?, ValueTask>>(target);
|
if (timer.ValueTaskCachedDelegate == null)
|
||||||
var task = func!(timer.State);
|
{
|
||||||
|
timer.ValueTaskCachedDelegate = timer.Method.As<Func<Object?, ValueTask>>(target);
|
||||||
|
}
|
||||||
|
|
||||||
|
//var func = timer.Method.As<Func<Object?, ValueTask>>(target);
|
||||||
|
var task = timer.ValueTaskCachedDelegate!(timer.State);
|
||||||
if (!task.IsCompleted)
|
if (!task.IsCompleted)
|
||||||
await task.ConfigureAwait(false);
|
await task.ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
#endif
|
#endif
|
||||||
{
|
{
|
||||||
var func = timer.Method.As<Func<Object?, Task>>(target);
|
if (timer.TaskCachedDelegate == null)
|
||||||
var task = func!(timer.State);
|
{
|
||||||
|
timer.TaskCachedDelegate = timer.Method.As<Func<Object?, Task>>(target);
|
||||||
|
}
|
||||||
|
|
||||||
|
//var func = timer.Method.As<Func<Object?, Task>>(target);
|
||||||
|
var task = timer.TaskCachedDelegate!(timer.State);
|
||||||
if (!task.IsCompleted)
|
if (!task.IsCompleted)
|
||||||
await task.ConfigureAwait(false);
|
await task.ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -542,6 +542,12 @@ public class TimerX : ITimer, ITimerx, IDisposable
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Func<object?, Task>? TaskCachedDelegate { get; internal set; }
|
||||||
|
#if NET6_0_OR_GREATER
|
||||||
|
public Func<object?, ValueTask>? ValueTaskCachedDelegate { get; internal set; }
|
||||||
|
#endif
|
||||||
|
public TimerCallback? TimerCallbackCachedDelegate { get; internal set; }
|
||||||
|
|
||||||
private static void CopyNow(Object? state) => _Now = TimerScheduler.Default.GetNow();
|
private static void CopyNow(Object? state) => _Now = TimerScheduler.Default.GetNow();
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
<Project>
|
<Project>
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<PluginVersion>10.11.108</PluginVersion>
|
<PluginVersion>10.11.110</PluginVersion>
|
||||||
<ProPluginVersion>10.11.108</ProPluginVersion>
|
<ProPluginVersion>10.11.110</ProPluginVersion>
|
||||||
<DefaultVersion>10.11.108</DefaultVersion>
|
<DefaultVersion>10.11.110</DefaultVersion>
|
||||||
<AuthenticationVersion>10.11.6</AuthenticationVersion>
|
<AuthenticationVersion>10.11.6</AuthenticationVersion>
|
||||||
<SourceGeneratorVersion>10.11.6</SourceGeneratorVersion>
|
<SourceGeneratorVersion>10.11.6</SourceGeneratorVersion>
|
||||||
<NET8Version>8.0.21</NET8Version>
|
<NET8Version>8.0.21</NET8Version>
|
||||||
@@ -12,7 +12,7 @@
|
|||||||
<IsTrimmable>false</IsTrimmable>
|
<IsTrimmable>false</IsTrimmable>
|
||||||
<ManagementProPluginVersion>10.11.87</ManagementProPluginVersion>
|
<ManagementProPluginVersion>10.11.87</ManagementProPluginVersion>
|
||||||
<ManagementPluginVersion>10.11.87</ManagementPluginVersion>
|
<ManagementPluginVersion>10.11.87</ManagementPluginVersion>
|
||||||
<TSVersion>4.0.0-beta.120</TSVersion>
|
<TSVersion>4.0.0-beta.130</TSVersion>
|
||||||
|
|
||||||
|
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|||||||
@@ -26,24 +26,65 @@ public static class ChannelOptionsExtensions
|
|||||||
/// <param name="e">接收数据</param>
|
/// <param name="e">接收数据</param>
|
||||||
/// <param name="funcs">事件</param>
|
/// <param name="funcs">事件</param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
internal static async Task OnChannelReceivedEvent(this IClientChannel clientChannel, ReceivedDataEventArgs e, ChannelReceivedEventHandler funcs)
|
internal static ValueTask OnChannelReceivedEvent(
|
||||||
|
this IClientChannel clientChannel,
|
||||||
|
ReceivedDataEventArgs e,
|
||||||
|
ChannelReceivedEventHandler funcs)
|
||||||
{
|
{
|
||||||
clientChannel.ThrowIfNull(nameof(IClientChannel));
|
clientChannel.ThrowIfNull(nameof(IClientChannel));
|
||||||
e.ThrowIfNull(nameof(ReceivedDataEventArgs));
|
e.ThrowIfNull(nameof(ReceivedDataEventArgs));
|
||||||
funcs.ThrowIfNull(nameof(ChannelReceivedEventHandler));
|
funcs.ThrowIfNull(nameof(ChannelReceivedEventHandler));
|
||||||
|
|
||||||
if (funcs.Count > 0)
|
if (funcs.Count == 0) return EasyValueTask.CompletedTask;
|
||||||
|
|
||||||
|
return InvokeHandlersSequentially(clientChannel, e, funcs);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ValueTask InvokeHandlersSequentially(
|
||||||
|
IClientChannel clientChannel, ReceivedDataEventArgs e, ChannelReceivedEventHandler funcs)
|
||||||
|
{
|
||||||
|
var enumerator = new HandlerEnumerator(clientChannel, e, funcs);
|
||||||
|
return enumerator.MoveNextAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
private struct HandlerEnumerator
|
||||||
|
{
|
||||||
|
private readonly IClientChannel _channel;
|
||||||
|
private readonly ReceivedDataEventArgs _e;
|
||||||
|
private readonly ChannelReceivedEventHandler _funcs;
|
||||||
|
private int _index;
|
||||||
|
|
||||||
|
public HandlerEnumerator(IClientChannel channel, ReceivedDataEventArgs e, ChannelReceivedEventHandler funcs)
|
||||||
{
|
{
|
||||||
for (int i = 0; i < funcs.Count; i++)
|
_channel = channel;
|
||||||
|
_e = e;
|
||||||
|
_funcs = funcs;
|
||||||
|
_index = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ValueTask MoveNextAsync()
|
||||||
|
{
|
||||||
|
_index++;
|
||||||
|
if (_index >= _funcs.Count) return default;
|
||||||
|
|
||||||
|
var func = _funcs[_index];
|
||||||
|
if (func == null) return MoveNextAsync();
|
||||||
|
|
||||||
|
bool isLast = _index == _funcs.Count - 1;
|
||||||
|
var vt = func.Invoke(_channel, _e, isLast);
|
||||||
|
if (vt.IsCompletedSuccessfully)
|
||||||
{
|
{
|
||||||
var func = funcs[i];
|
if (_e.Handled) return default;
|
||||||
if (func == null) continue;
|
return MoveNextAsync();
|
||||||
await func.Invoke(clientChannel, e, i == funcs.Count - 1).ConfigureAwait(false);
|
|
||||||
if (e.Handled)
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
return Awaited(vt);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async ValueTask Awaited(ValueTask vt)
|
||||||
|
{
|
||||||
|
await vt.ConfigureAwait(false);
|
||||||
|
if (!_e.Handled)
|
||||||
|
await MoveNextAsync().ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -53,7 +94,7 @@ public static class ChannelOptionsExtensions
|
|||||||
/// <param name="clientChannel">通道</param>
|
/// <param name="clientChannel">通道</param>
|
||||||
/// <param name="funcs">事件</param>
|
/// <param name="funcs">事件</param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
internal static async Task OnChannelEvent(this IClientChannel clientChannel, ChannelEventHandler funcs)
|
internal static async ValueTask OnChannelEvent(this IClientChannel clientChannel, ChannelEventHandler funcs)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ public interface IChannel : ISetupConfigObject, IDisposable, IClosableClient, IC
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 接收事件回调类
|
/// 接收事件回调类
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class ChannelReceivedEventHandler : List<Func<IClientChannel, ReceivedDataEventArgs, bool, Task>>
|
public class ChannelReceivedEventHandler : List<Func<IClientChannel, ReceivedDataEventArgs, bool, ValueTask>>
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -131,10 +131,10 @@ public class OtherChannel : SetupConfigObject, IClientChannel
|
|||||||
m_dataHandlingAdapter = adapter;
|
m_dataHandlingAdapter = adapter;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Task PrivateHandleReceivedData(ReadOnlyMemory<byte> byteBlock, IRequestInfo requestInfo)
|
private async Task PrivateHandleReceivedData(ReadOnlyMemory<byte> byteBlock, IRequestInfo requestInfo)
|
||||||
{
|
{
|
||||||
LastReceivedTime = DateTime.Now;
|
LastReceivedTime = DateTime.Now;
|
||||||
return this.OnChannelReceivedEvent(new ReceivedDataEventArgs(byteBlock, requestInfo), ChannelReceived);
|
await this.OnChannelReceivedEvent(new ReceivedDataEventArgs(byteBlock, requestInfo), ChannelReceived).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -308,7 +308,7 @@ public abstract class DeviceBase : AsyncAndSyncDisposableObject, IDevice
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 接收,非主动发送的情况,重写实现非主从并发通讯协议,如果通道存在其他设备并且不希望其他设备处理时,设置<see cref="TouchSocketEventArgs.Handled"/> 为true
|
/// 接收,非主动发送的情况,重写实现非主从并发通讯协议,如果通道存在其他设备并且不希望其他设备处理时,设置<see cref="TouchSocketEventArgs.Handled"/> 为true
|
||||||
/// </summary>
|
/// </summary>
|
||||||
protected virtual Task ChannelReceived(IClientChannel client, ReceivedDataEventArgs e, bool last)
|
protected virtual ValueTask ChannelReceived(IClientChannel client, ReceivedDataEventArgs e, bool last)
|
||||||
{
|
{
|
||||||
if (e.RequestInfo is MessageBase response)
|
if (e.RequestInfo is MessageBase response)
|
||||||
{
|
{
|
||||||
@@ -325,7 +325,7 @@ public abstract class DeviceBase : AsyncAndSyncDisposableObject, IDevice
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return EasyTask.CompletedTask;
|
return EasyValueTask.CompletedTask;
|
||||||
}
|
}
|
||||||
public bool AutoConnect { get; protected set; } = true;
|
public bool AutoConnect { get; protected set; } = true;
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
|
|||||||
@@ -107,7 +107,8 @@ public class CronScheduledTask : DisposeBase, IScheduledTask
|
|||||||
{
|
{
|
||||||
if (!Check())
|
if (!Check())
|
||||||
{
|
{
|
||||||
SetNext(next);
|
int nextValue = next;
|
||||||
|
SetNext(nextValue);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -149,7 +150,8 @@ public class CronScheduledTask : DisposeBase, IScheduledTask
|
|||||||
{
|
{
|
||||||
if (!Check())
|
if (!Check())
|
||||||
{
|
{
|
||||||
SetNext(next);
|
int nextValue = next;
|
||||||
|
SetNext(nextValue);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -93,7 +93,8 @@ public class ScheduledAsyncTask : DisposeBase, IScheduledTask, IScheduledIntInte
|
|||||||
{
|
{
|
||||||
if (!Check() && IntervalMS > 8)
|
if (!Check() && IntervalMS > 8)
|
||||||
{
|
{
|
||||||
SetNext(next);
|
int nextValue = next;
|
||||||
|
SetNext(nextValue);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -80,7 +80,8 @@ public class ScheduledSyncTask : DisposeBase, IScheduledTask, IScheduledIntInter
|
|||||||
{
|
{
|
||||||
if (!Check() && IntervalMS > 8)
|
if (!Check() && IntervalMS > 8)
|
||||||
{
|
{
|
||||||
SetNext(next);
|
int nextValue = next;
|
||||||
|
SetNext(nextValue);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -359,124 +359,314 @@ public abstract partial class CollectBase : DriverBase
|
|||||||
private readonly LinkedCancellationTokenSourceCache _linkedCtsCache = new();
|
private readonly LinkedCancellationTokenSourceCache _linkedCtsCache = new();
|
||||||
|
|
||||||
#region 执行默认读取
|
#region 执行默认读取
|
||||||
async ValueTask ReadVariableSource(object? state, CancellationToken cancellationToken)
|
//async ValueTask ReadVariableSource(object? state, CancellationToken cancellationToken)
|
||||||
|
//{
|
||||||
|
|
||||||
|
// if (state is not VariableSourceRead variableSourceRead) return;
|
||||||
|
|
||||||
|
// if (Pause) return;
|
||||||
|
// if (cancellationToken.IsCancellationRequested) return;
|
||||||
|
// CancellationToken readToken = default;
|
||||||
|
// var readerLockTask = ReadWriteLock.ReaderLockAsync(cancellationToken);
|
||||||
|
// if (!readerLockTask.IsCompleted)
|
||||||
|
// {
|
||||||
|
// readToken = await readerLockTask.ConfigureAwait(false);
|
||||||
|
// }
|
||||||
|
// else
|
||||||
|
// {
|
||||||
|
// readToken = readerLockTask.Result;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if (readToken.IsCancellationRequested)
|
||||||
|
// {
|
||||||
|
// await ReadVariableSource(state, cancellationToken).ConfigureAwait(false);
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// var allTokenSource = _linkedCtsCache.GetLinkedTokenSource(cancellationToken, readToken);
|
||||||
|
// var allToken = allTokenSource.Token;
|
||||||
|
|
||||||
|
// //if (LogMessage?.LogLevel <= TouchSocket.Core.LogLevel.Trace)
|
||||||
|
// // LogMessage?.Trace(string.Format("{0} - Collecting [{1} - {2}]", DeviceName, variableSourceRead?.RegisterAddress, variableSourceRead?.Length));
|
||||||
|
|
||||||
|
// OperResult<ReadOnlyMemory<byte>> readResult = default;
|
||||||
|
// var readTask = ReadSourceAsync(variableSourceRead, allToken);
|
||||||
|
// if (!readTask.IsCompleted)
|
||||||
|
// {
|
||||||
|
// readResult = await readTask.ConfigureAwait(false);
|
||||||
|
// }
|
||||||
|
// else
|
||||||
|
// {
|
||||||
|
// readResult = readTask.Result;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// var readErrorCount = 0;
|
||||||
|
|
||||||
|
// // 读取失败时重试一定次数
|
||||||
|
// while (!readResult.IsSuccess && readErrorCount < CollectProperties.RetryCount)
|
||||||
|
// {
|
||||||
|
// if (Pause)
|
||||||
|
// return;
|
||||||
|
// if (cancellationToken.IsCancellationRequested)
|
||||||
|
// return;
|
||||||
|
|
||||||
|
// if (readToken.IsCancellationRequested)
|
||||||
|
// {
|
||||||
|
// await ReadVariableSource(state, cancellationToken).ConfigureAwait(false);
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// readErrorCount++;
|
||||||
|
// if (LogMessage?.LogLevel <= TouchSocket.Core.LogLevel.Trace)
|
||||||
|
// LogMessage?.Trace(string.Format("{0} - Collection [{1} - {2}] failed - {3}", DeviceName, variableSourceRead?.RegisterAddress, variableSourceRead?.Length, readResult.ErrorMessage));
|
||||||
|
|
||||||
|
// //if (LogMessage?.LogLevel <= TouchSocket.Core.LogLevel.Trace)
|
||||||
|
// // LogMessage?.Trace(string.Format("{0} - Collecting [{1} - {2}]", DeviceName, variableSourceRead?.RegisterAddress, variableSourceRead?.Length));
|
||||||
|
// var readTask1 = ReadSourceAsync(variableSourceRead, allToken);
|
||||||
|
// if (!readTask1.IsCompleted)
|
||||||
|
// {
|
||||||
|
// readResult = await readTask1.ConfigureAwait(false);
|
||||||
|
// }
|
||||||
|
// else
|
||||||
|
// {
|
||||||
|
// readResult = readTask1.Result;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if (readResult.IsSuccess)
|
||||||
|
// {
|
||||||
|
// // 读取成功时记录日志并增加成功计数器
|
||||||
|
// if (LogMessage?.LogLevel <= TouchSocket.Core.LogLevel.Trace)
|
||||||
|
// LogMessage?.Trace(string.Format("{0} - Collection [{1} - {2}] data succeeded {3}", DeviceName, variableSourceRead?.RegisterAddress, variableSourceRead?.Length, readResult.Content.Span.ToHexString(' ')));
|
||||||
|
// CurrentDevice.SetDeviceStatus(TimerX.Now, null);
|
||||||
|
// }
|
||||||
|
// else
|
||||||
|
// {
|
||||||
|
// if (cancellationToken.IsCancellationRequested)
|
||||||
|
// return;
|
||||||
|
|
||||||
|
// if (readToken.IsCancellationRequested)
|
||||||
|
// {
|
||||||
|
// await ReadVariableSource(state, cancellationToken).ConfigureAwait(false);
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // 读取失败时记录日志并增加失败计数器,更新错误信息并清除变量状态
|
||||||
|
// if (variableSourceRead.LastErrorMessage != readResult.ErrorMessage)
|
||||||
|
// {
|
||||||
|
// if (!cancellationToken.IsCancellationRequested)
|
||||||
|
// LogMessage?.LogWarning(readResult.Exception, string.Format(AppResource.CollectFail, DeviceName, variableSourceRead?.RegisterAddress, variableSourceRead?.Length, readResult.ErrorMessage));
|
||||||
|
// }
|
||||||
|
// else
|
||||||
|
// {
|
||||||
|
// if (!cancellationToken.IsCancellationRequested)
|
||||||
|
// {
|
||||||
|
// if (LogMessage?.LogLevel <= TouchSocket.Core.LogLevel.Trace)
|
||||||
|
// LogMessage?.Trace(string.Format("{0} - Collection [{1} - {2}] data failed - {3}", DeviceName, variableSourceRead?.RegisterAddress, variableSourceRead?.Length, readResult.ErrorMessage));
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// variableSourceRead.LastErrorMessage = readResult.ErrorMessage;
|
||||||
|
// CurrentDevice.SetDeviceStatus(TimerX.Now, null, readResult.ErrorMessage);
|
||||||
|
// var time = DateTime.Now;
|
||||||
|
// foreach (var item in variableSourceRead.VariableRuntimes)
|
||||||
|
// {
|
||||||
|
// item.SetValue(null, time, isOnline: false);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
private ValueTask ReadVariableSource(object? state, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
var enumerator = new ReadVariableSourceEnumerator(this, state, cancellationToken);
|
||||||
|
return enumerator.MoveNextAsync();
|
||||||
|
}
|
||||||
|
|
||||||
if (state is not VariableSourceRead variableSourceRead) return;
|
private struct ReadVariableSourceEnumerator
|
||||||
|
{
|
||||||
|
private readonly CollectBase _owner;
|
||||||
|
private readonly object? _state;
|
||||||
|
private readonly CancellationToken _cancellationToken;
|
||||||
|
|
||||||
if (Pause) return;
|
private VariableSourceRead _variableSourceRead;
|
||||||
if (cancellationToken.IsCancellationRequested) return;
|
private CancellationToken _readToken;
|
||||||
CancellationToken readToken = default;
|
private CancellationToken _allToken;
|
||||||
var readerLockTask = ReadWriteLock.ReaderLockAsync(cancellationToken);
|
private OperResult<ReadOnlyMemory<byte>> _readResult;
|
||||||
if (!readerLockTask.IsCompleted)
|
private int _readErrorCount;
|
||||||
|
private ValueTask<CancellationToken> _readerLockTask;
|
||||||
|
private ValueTask<OperResult<ReadOnlyMemory<byte>>> _readTask;
|
||||||
|
private int _step;
|
||||||
|
|
||||||
|
public ReadVariableSourceEnumerator(CollectBase owner, object? state, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
readToken = await readerLockTask.ConfigureAwait(false);
|
_owner = owner;
|
||||||
}
|
_state = state;
|
||||||
else
|
_cancellationToken = cancellationToken;
|
||||||
{
|
|
||||||
readToken = readerLockTask.Result;
|
_variableSourceRead = default!;
|
||||||
|
_readToken = default;
|
||||||
|
_allToken = default;
|
||||||
|
_readResult = default;
|
||||||
|
_readErrorCount = 0;
|
||||||
|
_readerLockTask = default;
|
||||||
|
_readTask = default;
|
||||||
|
_step = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (readToken.IsCancellationRequested)
|
public ValueTask MoveNextAsync()
|
||||||
{
|
{
|
||||||
await ReadVariableSource(state, cancellationToken).ConfigureAwait(false);
|
switch (_step)
|
||||||
return;
|
{
|
||||||
|
case 0:
|
||||||
|
if (_state is not VariableSourceRead vsr) return default;
|
||||||
|
_variableSourceRead = vsr;
|
||||||
|
|
||||||
|
if (_owner.Pause) return default;
|
||||||
|
if (_cancellationToken.IsCancellationRequested) return default;
|
||||||
|
|
||||||
|
#pragma warning disable CA2012 // 正确使用 ValueTask
|
||||||
|
_readerLockTask = _owner.ReadWriteLock.ReaderLockAsync(_cancellationToken);
|
||||||
|
#pragma warning restore CA2012 // 正确使用 ValueTask
|
||||||
|
if (!_readerLockTask.IsCompleted)
|
||||||
|
{
|
||||||
|
_step = 1;
|
||||||
|
return AwaitReaderLock();
|
||||||
|
}
|
||||||
|
_readToken = _readerLockTask.Result;
|
||||||
|
goto case 2;
|
||||||
|
|
||||||
|
case 1:
|
||||||
|
_readToken = _readerLockTask.Result;
|
||||||
|
goto case 2;
|
||||||
|
|
||||||
|
case 2:
|
||||||
|
if (_readToken.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
return _owner.ReadVariableSource(_state, _cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
var allTokenSource = _owner._linkedCtsCache.GetLinkedTokenSource(_cancellationToken, _readToken);
|
||||||
|
_allToken = allTokenSource.Token;
|
||||||
|
|
||||||
|
#pragma warning disable CA2012 // 正确使用 ValueTask
|
||||||
|
_readTask = _owner.ReadSourceAsync(_variableSourceRead, _allToken);
|
||||||
|
#pragma warning restore CA2012 // 正确使用 ValueTask
|
||||||
|
if (!_readTask.IsCompleted)
|
||||||
|
{
|
||||||
|
_step = 3;
|
||||||
|
return AwaitRead();
|
||||||
|
}
|
||||||
|
_readResult = _readTask.Result;
|
||||||
|
goto case 4;
|
||||||
|
|
||||||
|
case 3:
|
||||||
|
_readResult = _readTask.Result;
|
||||||
|
goto case 4;
|
||||||
|
|
||||||
|
case 4:
|
||||||
|
while (!_readResult.IsSuccess && _readErrorCount < _owner.CollectProperties.RetryCount)
|
||||||
|
{
|
||||||
|
if (_owner.Pause) return default;
|
||||||
|
if (_cancellationToken.IsCancellationRequested) return default;
|
||||||
|
|
||||||
|
if (_readToken.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
return _owner.ReadVariableSource(_state, _cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
_readErrorCount++;
|
||||||
|
if (_owner.LogMessage?.LogLevel <= TouchSocket.Core.LogLevel.Trace)
|
||||||
|
_owner.LogMessage?.Trace(string.Format("{0} - Collection [{1} - {2}] failed - {3}",
|
||||||
|
_owner.DeviceName, _variableSourceRead?.RegisterAddress, _variableSourceRead?.Length, _readResult.ErrorMessage));
|
||||||
|
|
||||||
|
#pragma warning disable CA2012 // 正确使用 ValueTask
|
||||||
|
_readTask = _owner.ReadSourceAsync(_variableSourceRead, _allToken);
|
||||||
|
#pragma warning restore CA2012 // 正确使用 ValueTask
|
||||||
|
if (!_readTask.IsCompleted)
|
||||||
|
{
|
||||||
|
_step = 5;
|
||||||
|
return AwaitReadRetry();
|
||||||
|
}
|
||||||
|
_readResult = _readTask.Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
goto case 6;
|
||||||
|
|
||||||
|
case 5:
|
||||||
|
_readResult = _readTask.Result;
|
||||||
|
_step = 4;
|
||||||
|
return MoveNextAsync();
|
||||||
|
|
||||||
|
case 6:
|
||||||
|
if (_readResult.IsSuccess)
|
||||||
|
{
|
||||||
|
if (_owner.LogMessage?.LogLevel <= TouchSocket.Core.LogLevel.Trace)
|
||||||
|
_owner.LogMessage?.Trace(string.Format("{0} - Collection [{1} - {2}] data succeeded {3}",
|
||||||
|
_owner.DeviceName, _variableSourceRead?.RegisterAddress, _variableSourceRead?.Length, _readResult.Content.Span.ToHexString(' ')));
|
||||||
|
|
||||||
|
_owner.CurrentDevice.SetDeviceStatus(TimerX.Now, null);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (_cancellationToken.IsCancellationRequested) return default;
|
||||||
|
if (_readToken.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
return _owner.ReadVariableSource(_state, _cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_variableSourceRead.LastErrorMessage != _readResult.ErrorMessage)
|
||||||
|
{
|
||||||
|
if (!_cancellationToken.IsCancellationRequested)
|
||||||
|
_owner.LogMessage?.LogWarning(_readResult.Exception, string.Format(AppResource.CollectFail, _owner.DeviceName,
|
||||||
|
_variableSourceRead?.RegisterAddress, _variableSourceRead?.Length, _readResult.ErrorMessage));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (!_cancellationToken.IsCancellationRequested && _owner.LogMessage?.LogLevel <= TouchSocket.Core.LogLevel.Trace)
|
||||||
|
_owner.LogMessage?.Trace(string.Format("{0} - Collection [{1} - {2}] data failed - {3}",
|
||||||
|
_owner.DeviceName, _variableSourceRead?.RegisterAddress, _variableSourceRead?.Length, _readResult.ErrorMessage));
|
||||||
|
}
|
||||||
|
|
||||||
|
_variableSourceRead.LastErrorMessage = _readResult.ErrorMessage;
|
||||||
|
_owner.CurrentDevice.SetDeviceStatus(TimerX.Now, null, _readResult.ErrorMessage);
|
||||||
|
var time = DateTime.Now;
|
||||||
|
foreach (var item in _variableSourceRead.VariableRuntimes)
|
||||||
|
{
|
||||||
|
item.SetValue(null, time, isOnline: false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return default;
|
||||||
}
|
}
|
||||||
|
|
||||||
var allTokenSource = _linkedCtsCache.GetLinkedTokenSource(cancellationToken, readToken);
|
private async ValueTask AwaitReaderLock()
|
||||||
var allToken = allTokenSource.Token;
|
|
||||||
|
|
||||||
//if (LogMessage?.LogLevel <= TouchSocket.Core.LogLevel.Trace)
|
|
||||||
// LogMessage?.Trace(string.Format("{0} - Collecting [{1} - {2}]", DeviceName, variableSourceRead?.RegisterAddress, variableSourceRead?.Length));
|
|
||||||
|
|
||||||
OperResult<ReadOnlyMemory<byte>> readResult = default;
|
|
||||||
var readTask = ReadSourceAsync(variableSourceRead, allToken);
|
|
||||||
if (!readTask.IsCompleted)
|
|
||||||
{
|
{
|
||||||
readResult = await readTask.ConfigureAwait(false);
|
await _readerLockTask.ConfigureAwait(false);
|
||||||
}
|
_step = 1;
|
||||||
else
|
await MoveNextAsync().ConfigureAwait(false);
|
||||||
{
|
|
||||||
readResult = readTask.Result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var readErrorCount = 0;
|
private async ValueTask AwaitRead()
|
||||||
|
|
||||||
// 读取失败时重试一定次数
|
|
||||||
while (!readResult.IsSuccess && readErrorCount < CollectProperties.RetryCount)
|
|
||||||
{
|
{
|
||||||
if (Pause)
|
await _readTask.ConfigureAwait(false);
|
||||||
return;
|
_step = 3;
|
||||||
if (cancellationToken.IsCancellationRequested)
|
await MoveNextAsync().ConfigureAwait(false);
|
||||||
return;
|
|
||||||
|
|
||||||
if (readToken.IsCancellationRequested)
|
|
||||||
{
|
|
||||||
await ReadVariableSource(state, cancellationToken).ConfigureAwait(false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
readErrorCount++;
|
|
||||||
if (LogMessage?.LogLevel <= TouchSocket.Core.LogLevel.Trace)
|
|
||||||
LogMessage?.Trace(string.Format("{0} - Collection [{1} - {2}] failed - {3}", DeviceName, variableSourceRead?.RegisterAddress, variableSourceRead?.Length, readResult.ErrorMessage));
|
|
||||||
|
|
||||||
//if (LogMessage?.LogLevel <= TouchSocket.Core.LogLevel.Trace)
|
|
||||||
// LogMessage?.Trace(string.Format("{0} - Collecting [{1} - {2}]", DeviceName, variableSourceRead?.RegisterAddress, variableSourceRead?.Length));
|
|
||||||
var readTask1 = ReadSourceAsync(variableSourceRead, allToken);
|
|
||||||
if (!readTask1.IsCompleted)
|
|
||||||
{
|
|
||||||
readResult = await readTask1.ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
readResult = readTask1.Result;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (readResult.IsSuccess)
|
private async ValueTask AwaitReadRetry()
|
||||||
{
|
{
|
||||||
// 读取成功时记录日志并增加成功计数器
|
await _readTask.ConfigureAwait(false);
|
||||||
if (LogMessage?.LogLevel <= TouchSocket.Core.LogLevel.Trace)
|
_step = 5;
|
||||||
LogMessage?.Trace(string.Format("{0} - Collection [{1} - {2}] data succeeded {3}", DeviceName, variableSourceRead?.RegisterAddress, variableSourceRead?.Length, readResult.Content.Span.ToHexString(' ')));
|
await MoveNextAsync().ConfigureAwait(false);
|
||||||
CurrentDevice.SetDeviceStatus(TimerX.Now, null);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (cancellationToken.IsCancellationRequested)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (readToken.IsCancellationRequested)
|
|
||||||
{
|
|
||||||
await ReadVariableSource(state, cancellationToken).ConfigureAwait(false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 读取失败时记录日志并增加失败计数器,更新错误信息并清除变量状态
|
|
||||||
if (variableSourceRead.LastErrorMessage != readResult.ErrorMessage)
|
|
||||||
{
|
|
||||||
if (!cancellationToken.IsCancellationRequested)
|
|
||||||
LogMessage?.LogWarning(readResult.Exception, string.Format(AppResource.CollectFail, DeviceName, variableSourceRead?.RegisterAddress, variableSourceRead?.Length, readResult.ErrorMessage));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (!cancellationToken.IsCancellationRequested)
|
|
||||||
{
|
|
||||||
if (LogMessage?.LogLevel <= TouchSocket.Core.LogLevel.Trace)
|
|
||||||
LogMessage?.Trace(string.Format("{0} - Collection [{1} - {2}] data failed - {3}", DeviceName, variableSourceRead?.RegisterAddress, variableSourceRead?.Length, readResult.ErrorMessage));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
variableSourceRead.LastErrorMessage = readResult.ErrorMessage;
|
|
||||||
CurrentDevice.SetDeviceStatus(TimerX.Now, null, readResult.ErrorMessage);
|
|
||||||
var time = DateTime.Now;
|
|
||||||
foreach (var item in variableSourceRead.VariableRuntimes)
|
|
||||||
{
|
|
||||||
item.SetValue(null, time, isOnline: false);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -147,43 +147,151 @@ public abstract class CollectFoundationBase : CollectBase
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//protected override async ValueTask<OperResult<ReadOnlyMemory<byte>>> ReadSourceAsync(VariableSourceRead variableSourceRead, CancellationToken cancellationToken)
|
||||||
|
//{
|
||||||
|
// try
|
||||||
|
// {
|
||||||
|
|
||||||
|
// if (cancellationToken.IsCancellationRequested)
|
||||||
|
// return new(new OperationCanceledException());
|
||||||
|
|
||||||
|
// // 从协议读取数据
|
||||||
|
// OperResult<ReadOnlyMemory<byte>> read = default;
|
||||||
|
// var readTask = FoundationDevice.ReadAsync(variableSourceRead.AddressObject, cancellationToken);
|
||||||
|
// if (!readTask.IsCompleted)
|
||||||
|
// {
|
||||||
|
// read = await readTask.ConfigureAwait(false);
|
||||||
|
// }
|
||||||
|
// else
|
||||||
|
// {
|
||||||
|
// read = readTask.Result;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // 如果读取成功且有有效内容,则解析结构化内容
|
||||||
|
// if (read.IsSuccess)
|
||||||
|
// {
|
||||||
|
// var prase = variableSourceRead.VariableRuntimes.PraseStructContent(FoundationDevice, read.Content.Span, false);
|
||||||
|
// return new OperResult<ReadOnlyMemory<byte>>(prase);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // 返回读取结果
|
||||||
|
// return read;
|
||||||
|
// }
|
||||||
|
// catch (Exception ex)
|
||||||
|
// {
|
||||||
|
// // 捕获异常并返回失败结果
|
||||||
|
// return new OperResult<ReadOnlyMemory<byte>>(ex);
|
||||||
|
// }
|
||||||
|
//}
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 采集驱动读取,读取成功后直接赋值变量,失败不做处理,注意非通用设备需重写
|
/// 采集驱动读取,读取成功后直接赋值变量,失败不做处理,注意非通用设备需重写
|
||||||
/// </summary>
|
/// </summary>
|
||||||
protected override async ValueTask<OperResult<ReadOnlyMemory<byte>>> ReadSourceAsync(VariableSourceRead variableSourceRead, CancellationToken cancellationToken)
|
protected override ValueTask<OperResult<ReadOnlyMemory<byte>>> ReadSourceAsync(VariableSourceRead variableSourceRead, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
try
|
if (cancellationToken.IsCancellationRequested)
|
||||||
|
return new ValueTask<OperResult<ReadOnlyMemory<byte>>>( new OperResult<ReadOnlyMemory<byte>>(new OperationCanceledException()));
|
||||||
|
|
||||||
|
// 值类型状态机
|
||||||
|
var stateMachine = new ReadSourceStateMachine(this, variableSourceRead, cancellationToken);
|
||||||
|
return stateMachine.MoveNextAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
private struct ReadSourceStateMachine
|
||||||
|
{
|
||||||
|
private readonly VariableSourceRead _variableSourceRead;
|
||||||
|
private readonly CancellationToken _cancellationToken;
|
||||||
|
private readonly CollectFoundationBase _owner;
|
||||||
|
private OperResult<ReadOnlyMemory<byte>> _result;
|
||||||
|
private ValueTask<OperResult<ReadOnlyMemory<byte>>> _readTask;
|
||||||
|
|
||||||
|
public ReadSourceStateMachine(CollectFoundationBase owner, VariableSourceRead variableSourceRead, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
_owner = owner;
|
||||||
if (cancellationToken.IsCancellationRequested)
|
_variableSourceRead = variableSourceRead;
|
||||||
return new(new OperationCanceledException());
|
_cancellationToken = cancellationToken;
|
||||||
|
_result = default;
|
||||||
// 从协议读取数据
|
State = 0;
|
||||||
OperResult<ReadOnlyMemory<byte>> read = default;
|
|
||||||
var readTask = FoundationDevice.ReadAsync(variableSourceRead.AddressObject, cancellationToken);
|
|
||||||
if (!readTask.IsCompleted)
|
|
||||||
{
|
|
||||||
read = await readTask.ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
read = readTask.Result;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果读取成功且有有效内容,则解析结构化内容
|
|
||||||
if (read.IsSuccess)
|
|
||||||
{
|
|
||||||
var prase = variableSourceRead.VariableRuntimes.PraseStructContent(FoundationDevice, read.Content.Span, false);
|
|
||||||
return new OperResult<ReadOnlyMemory<byte>>(prase);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 返回读取结果
|
|
||||||
return read;
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
|
||||||
|
public int State { get; private set; }
|
||||||
|
|
||||||
|
public ValueTask<OperResult<ReadOnlyMemory<byte>>> MoveNextAsync()
|
||||||
{
|
{
|
||||||
// 捕获异常并返回失败结果
|
try
|
||||||
return new OperResult<ReadOnlyMemory<byte>>(ex);
|
{
|
||||||
|
switch (State)
|
||||||
|
{
|
||||||
|
case 0:
|
||||||
|
// 异步读取
|
||||||
|
if (_cancellationToken.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
_result = new OperResult<ReadOnlyMemory<byte>>(new OperationCanceledException());
|
||||||
|
return new ValueTask<OperResult<ReadOnlyMemory<byte>>>(_result);
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma warning disable CA2012 // 正确使用 ValueTask
|
||||||
|
_readTask = _owner.FoundationDevice.ReadAsync(_variableSourceRead.AddressObject, _cancellationToken);
|
||||||
|
#pragma warning restore CA2012 // 正确使用 ValueTask
|
||||||
|
|
||||||
|
// 检查是否任务已完成
|
||||||
|
if (_readTask.IsCompleted)
|
||||||
|
{
|
||||||
|
_result = _readTask.Result;
|
||||||
|
State = 1;
|
||||||
|
return MoveNextAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果任务尚未完成,继续等待
|
||||||
|
State = 2;
|
||||||
|
return Awaited(_readTask);
|
||||||
|
|
||||||
|
case 1:
|
||||||
|
// 解析结构化内容
|
||||||
|
if (_result.IsSuccess)
|
||||||
|
{
|
||||||
|
var parsedResult = _variableSourceRead.VariableRuntimes.PraseStructContent(_owner.FoundationDevice, _result.Content.Span, false);
|
||||||
|
return new ValueTask<OperResult<ReadOnlyMemory<byte>>>(new OperResult<ReadOnlyMemory<byte>>(parsedResult));
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ValueTask<OperResult<ReadOnlyMemory<byte>>>(_result);
|
||||||
|
|
||||||
|
case 2:
|
||||||
|
// 完成任务后,解析内容
|
||||||
|
_result = _readTask.Result;
|
||||||
|
|
||||||
|
if (_result.IsSuccess)
|
||||||
|
{
|
||||||
|
var parsedResult = _variableSourceRead.VariableRuntimes.PraseStructContent(_owner.FoundationDevice, _result.Content.Span, false);
|
||||||
|
return new ValueTask<OperResult<ReadOnlyMemory<byte>>>(new OperResult<ReadOnlyMemory<byte>>(parsedResult));
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ValueTask<OperResult<ReadOnlyMemory<byte>>>(_result);
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new InvalidOperationException("Unexpected state.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return new ValueTask<OperResult<ReadOnlyMemory<byte>>>(new OperResult<ReadOnlyMemory<byte>>(ex));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async ValueTask<OperResult<ReadOnlyMemory<byte>>> Awaited(ValueTask<OperResult<ReadOnlyMemory<byte>>> vt)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
|
||||||
|
|
||||||
|
await vt.ConfigureAwait(false);
|
||||||
|
return await MoveNextAsync().ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return new OperResult<ReadOnlyMemory<byte>>(ex);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
@inherits ThingsGatewayModuleComponentBase
|
@inherits ThingsGatewayModuleComponentBase
|
||||||
@attribute [JSModuleAutoLoader("Components/QuickActions.razor.js", AutoInvokeDispose = false)]
|
@attribute [JSModuleAutoLoader("Components/QuickActions.razor.js", AutoInvokeDispose = false, AutoInvokeInit = true)]
|
||||||
@namespace ThingsGateway.Gateway.Razor
|
@namespace ThingsGateway.Gateway.Razor
|
||||||
|
|
||||||
<div id="@Id">
|
<div id="@Id">
|
||||||
@@ -8,12 +8,12 @@
|
|||||||
<div class="quickactions-list">
|
<div class="quickactions-list">
|
||||||
<div class="quickactions-header">
|
<div class="quickactions-header">
|
||||||
<div class="flex-fill">@HeaderText</div>
|
<div class="flex-fill">@HeaderText</div>
|
||||||
<button class="btn-close btn-close-white" type="button" aria-label="Close" onclick=@(async ()=>await ToggleOpen())></button>
|
<button class="btn-close btn-close-white" type="button" aria-label="Close" onclick=@(async () => await ToggleOpen())></button>
|
||||||
</div>
|
</div>
|
||||||
<div class="mx-2 row g-0">
|
<div class="mx-2 row g-0">
|
||||||
|
|
||||||
<div class="col-12 my-1">
|
<div class="col-12 my-1">
|
||||||
<RadioList ShowLabel="true" TValue="bool" IsButton ValueExpression=@(()=> AutoRestartThread ) Value="AutoRestartThread" ValueChanged="OnAutoRestartThreadChanged" Items="AutoRestartThreadBoolItems" />
|
<RadioList ShowLabel="true" TValue="bool" IsButton ValueExpression=@(() => AutoRestartThread) Value="AutoRestartThread" ValueChanged="OnAutoRestartThreadChanged" Items="AutoRestartThreadBoolItems" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,8 @@
|
|||||||
@using ThingsGateway.Admin.Application
|
@using ThingsGateway.Admin.Application
|
||||||
@using ThingsGateway.Admin.Razor
|
@using ThingsGateway.Admin.Razor
|
||||||
@using ThingsGateway.Gateway.Application
|
@using ThingsGateway.Gateway.Application
|
||||||
|
@attribute [JSModuleAutoLoader("Pages/GatewayMonitorPage/ChannelDeviceTree.razor.js", AutoInvokeInit = true, AutoInvokeDispose = false, JSObjectReference = true)]
|
||||||
|
@inherits ThingsGatewayModuleComponentBase
|
||||||
<div class="listtree-view">
|
<div class="listtree-view">
|
||||||
|
|
||||||
<div class="d-flex align-items-center">
|
<div class="d-flex align-items-center">
|
||||||
@@ -13,7 +14,7 @@
|
|||||||
|
|
||||||
|
|
||||||
<ContextMenuZone>
|
<ContextMenuZone>
|
||||||
<TreeView TItem="ChannelDeviceTreeItem" Items="Items" ShowIcon="false" ShowSearch IsAccordion=false IsVirtualize="true" OnTreeItemClick="OnTreeItemClick" OnSearchAsync="OnClickSearch" ModelEqualityComparer=ModelEqualityComparer ShowToolbar="true" >
|
<TreeView Id=@Id TItem="ChannelDeviceTreeItem" Items="Items" ShowIcon="false" ShowSearch IsAccordion=false IsVirtualize="true" OnTreeItemClick="OnTreeItemClick" OnSearchAsync="OnClickSearch" ModelEqualityComparer=ModelEqualityComparer ShowToolbar="true" >
|
||||||
<ToolbarTemplate>
|
<ToolbarTemplate>
|
||||||
|
|
||||||
<div class="tree-node-toolbar-edit" @onclick:preventDefault @onclick:stopPropagation>
|
<div class="tree-node-toolbar-edit" @onclick:preventDefault @onclick:stopPropagation>
|
||||||
@@ -137,6 +138,6 @@
|
|||||||
@code {
|
@code {
|
||||||
RenderFragment<ChannelDeviceTreeItem> RenderTreeItem = (item) =>
|
RenderFragment<ChannelDeviceTreeItem> RenderTreeItem = (item) =>
|
||||||
|
|
||||||
@<span class=@(GetClass(item))>@item.ToString()</span> ;
|
@<span class=@(GetClass(item)) id=@(GetId(item))>@item.ToString()</span> ;
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -10,6 +10,7 @@
|
|||||||
|
|
||||||
using Microsoft.AspNetCore.Components.Forms;
|
using Microsoft.AspNetCore.Components.Forms;
|
||||||
using Microsoft.AspNetCore.Components.Web;
|
using Microsoft.AspNetCore.Components.Web;
|
||||||
|
using Microsoft.JSInterop;
|
||||||
|
|
||||||
using ThingsGateway.Admin.Application;
|
using ThingsGateway.Admin.Application;
|
||||||
using ThingsGateway.Admin.Razor;
|
using ThingsGateway.Admin.Razor;
|
||||||
@@ -19,7 +20,7 @@ using ThingsGateway.SqlSugar;
|
|||||||
|
|
||||||
namespace ThingsGateway.Gateway.Razor;
|
namespace ThingsGateway.Gateway.Razor;
|
||||||
|
|
||||||
public partial class ChannelDeviceTree : IDisposable
|
public partial class ChannelDeviceTree
|
||||||
{
|
{
|
||||||
SpinnerComponent Spinner;
|
SpinnerComponent Spinner;
|
||||||
[Inject]
|
[Inject]
|
||||||
@@ -297,7 +298,7 @@ public partial class ChannelDeviceTree : IDisposable
|
|||||||
{
|
{
|
||||||
if (item.TryGetChannelRuntime(out var channelRuntime))
|
if (item.TryGetChannelRuntime(out var channelRuntime))
|
||||||
{
|
{
|
||||||
return channelRuntime.DeviceThreadManage != null ? "enable--text" : "disabled--text";
|
return channelRuntime.DeviceThreadManage != null ? " enable--text" : " disabled--text ";
|
||||||
}
|
}
|
||||||
else if (item.TryGetDeviceRuntime(out var deviceRuntime))
|
else if (item.TryGetDeviceRuntime(out var deviceRuntime))
|
||||||
{
|
{
|
||||||
@@ -1391,24 +1392,6 @@ EventCallback.Factory.Create<MouseEventArgs>(this, async e =>
|
|||||||
|
|
||||||
scheduler = new SmartTriggerScheduler(Notify, TimeSpan.FromMilliseconds(3000));
|
scheduler = new SmartTriggerScheduler(Notify, TimeSpan.FromMilliseconds(3000));
|
||||||
|
|
||||||
_ = Task.Run(async () =>
|
|
||||||
{
|
|
||||||
while (!Disposed)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await InvokeAsync(StateHasChanged);
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
await Task.Delay(5000);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
await base.OnInitializedAsync();
|
await base.OnInitializedAsync();
|
||||||
}
|
}
|
||||||
private async Task Notify(CancellationToken cancellationToken)
|
private async Task Notify(CancellationToken cancellationToken)
|
||||||
@@ -1423,7 +1406,7 @@ EventCallback.Factory.Create<MouseEventArgs>(this, async e =>
|
|||||||
{
|
{
|
||||||
await ChannelDeviceChanged.Invoke(Value);
|
await ChannelDeviceChanged.Invoke(Value);
|
||||||
}
|
}
|
||||||
|
await InvokeAsync(StateHasChanged);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ChannelDeviceTreeItem GetValue(ChannelDeviceTreeItem channelDeviceTreeItem)
|
private static ChannelDeviceTreeItem GetValue(ChannelDeviceTreeItem channelDeviceTreeItem)
|
||||||
@@ -1595,10 +1578,16 @@ EventCallback.Factory.Create<MouseEventArgs>(this, async e =>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
private bool Disposed;
|
private bool Disposed;
|
||||||
public void Dispose()
|
|
||||||
|
protected override async ValueTask DisposeAsync(bool disposing)
|
||||||
{
|
{
|
||||||
|
|
||||||
Disposed = true;
|
Disposed = true;
|
||||||
ChannelRuntimeDispatchService.UnSubscribe(Refresh);
|
ChannelRuntimeDispatchService.UnSubscribe(Refresh);
|
||||||
|
|
||||||
|
await Module.InvokeVoidAsync("dispose", Id);
|
||||||
|
|
||||||
|
await base.DisposeAsync(disposing);
|
||||||
}
|
}
|
||||||
|
|
||||||
ChannelDeviceTreeItem? SelectModel = default;
|
ChannelDeviceTreeItem? SelectModel = default;
|
||||||
@@ -1616,5 +1605,28 @@ EventCallback.Factory.Create<MouseEventArgs>(this, async e =>
|
|||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#region js
|
||||||
|
|
||||||
|
protected override Task InvokeInitAsync() => InvokeVoidAsync("init", Id, Interop, new { Method = nameof(TriggerStateChanged) });
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
[JSInvokable]
|
||||||
|
public List<string> TriggerStateChanged(List<string> jsstring)
|
||||||
|
{
|
||||||
|
List<string> ret = new(jsstring.Count);
|
||||||
|
foreach (var str in jsstring)
|
||||||
|
{
|
||||||
|
var item = ChannelDeviceTreeItem.FromJSString(str);
|
||||||
|
ret.Add(GetClass(item));
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string GetId(ChannelDeviceTreeItem channelDeviceTreeItem)
|
||||||
|
{
|
||||||
|
return ChannelDeviceTreeItem.ToJSString(channelDeviceTreeItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,57 @@
|
|||||||
|
let handlers = {};
|
||||||
|
|
||||||
|
export function init(id, invoke, options) {
|
||||||
|
//function getCellByClass(row, className) {
|
||||||
|
// // 直接用 querySelector 精确查找
|
||||||
|
// return row.querySelector(`td.${className}`);
|
||||||
|
//}
|
||||||
|
var variableHandler = setInterval(async () => {
|
||||||
|
var treeview = document.getElementById(id);
|
||||||
|
if (!treeview) {
|
||||||
|
clearInterval(variableHandler);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const spans = treeview.querySelectorAll(
|
||||||
|
'.tree-content[style*="--bb-tree-view-level: 2"] .tree-node > span, ' +
|
||||||
|
'.tree-content[style*="--bb-tree-view-level: 3"] .tree-node > span'
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!spans) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ids = Array.from(spans).map(span => span.id);
|
||||||
|
|
||||||
|
var { method } = options;
|
||||||
|
|
||||||
|
if (!invoke) return;
|
||||||
|
var valss = await invoke.invokeMethodAsync(method, ids);
|
||||||
|
if (!valss || valss.length === 0) return;
|
||||||
|
// 遍历 valss,下标 i 对应 span[i]
|
||||||
|
for (let i = 0; i < valss.length && i < spans.length; i++) {
|
||||||
|
const val = valss[i];
|
||||||
|
const span = spans[i];
|
||||||
|
|
||||||
|
if (!span) continue;
|
||||||
|
|
||||||
|
if (span.className !== val) {
|
||||||
|
span.className = val;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
, 1000) //1000ms刷新一次
|
||||||
|
|
||||||
|
handlers[id] = { variableHandler, invoke };
|
||||||
|
|
||||||
|
}
|
||||||
|
export function dispose(id) {
|
||||||
|
const handler = handlers[id];
|
||||||
|
if (handler) {
|
||||||
|
clearInterval(handler.timer);
|
||||||
|
handler.invoke = null;
|
||||||
|
delete handlers[id];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -141,4 +141,77 @@ public class ChannelDeviceTreeItem : IEqualityComparer<ChannelDeviceTreeItem>
|
|||||||
{
|
{
|
||||||
return HashCode.Combine(obj.ChannelDevicePluginType, obj.DeviceRuntimeId, obj.ChannelRuntimeId, obj.PluginName, obj.PluginType);
|
return HashCode.Combine(obj.ChannelDevicePluginType, obj.DeviceRuntimeId, obj.ChannelRuntimeId, obj.PluginName, obj.PluginType);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public static string ToJSString(ChannelDeviceTreeItem channelDeviceTreeItem)
|
||||||
|
{
|
||||||
|
return $"{channelDeviceTreeItem.ChannelDevicePluginType}.{channelDeviceTreeItem.DeviceRuntimeId}.{channelDeviceTreeItem.ChannelRuntimeId}.{channelDeviceTreeItem.PluginName}.{channelDeviceTreeItem.PluginType}";
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ChannelDeviceTreeItem FromJSString(string jsString)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(jsString))
|
||||||
|
throw new ArgumentNullException(nameof(jsString));
|
||||||
|
|
||||||
|
ReadOnlySpan<char> span = jsString.AsSpan();
|
||||||
|
Span<Range> ranges = stackalloc Range[5];
|
||||||
|
|
||||||
|
// 手动分割
|
||||||
|
int partIndex = 0;
|
||||||
|
int start = 0;
|
||||||
|
while (partIndex < 4) // 只找前4个分隔符
|
||||||
|
{
|
||||||
|
int idx = span[start..].IndexOf('.');
|
||||||
|
if (idx == -1)
|
||||||
|
throw new FormatException($"Invalid format: expected 5 parts, got {partIndex + 1}");
|
||||||
|
|
||||||
|
ranges[partIndex] = new Range(start, start + idx);
|
||||||
|
start += idx + 1;
|
||||||
|
partIndex++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 最后一段
|
||||||
|
ranges[partIndex] = new Range(start, span.Length);
|
||||||
|
|
||||||
|
// 校验段数
|
||||||
|
if (partIndex != 4)
|
||||||
|
throw new FormatException($"Invalid format: expected 5 parts, got {partIndex + 1}");
|
||||||
|
|
||||||
|
var part0 = span[ranges[0]];
|
||||||
|
var part1 = span[ranges[1]];
|
||||||
|
var part2 = span[ranges[2]];
|
||||||
|
var part3 = span[ranges[3]];
|
||||||
|
var part4 = span[ranges[4]];
|
||||||
|
|
||||||
|
// 解析 Enum 和 long
|
||||||
|
if (!Enum.TryParse(part0, out ChannelDevicePluginTypeEnum pluginType))
|
||||||
|
throw new FormatException($"Invalid {nameof(ChannelDevicePluginTypeEnum)}: {part0.ToString()}");
|
||||||
|
|
||||||
|
if (!long.TryParse(part1, out long deviceRuntimeId))
|
||||||
|
throw new FormatException($"Invalid DeviceRuntimeId: {part1.ToString()}");
|
||||||
|
|
||||||
|
if (!long.TryParse(part2, out long channelRuntimeId))
|
||||||
|
throw new FormatException($"Invalid ChannelRuntimeId: {part2.ToString()}");
|
||||||
|
|
||||||
|
string pluginName = part3.ToString();
|
||||||
|
|
||||||
|
PluginTypeEnum? parsedPluginType = null;
|
||||||
|
if (!part4.IsEmpty)
|
||||||
|
{
|
||||||
|
if (!Enum.TryParse(part4, out PluginTypeEnum tmp))
|
||||||
|
throw new FormatException($"Invalid {nameof(PluginTypeEnum)}: {part4.ToString()}");
|
||||||
|
parsedPluginType = tmp;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ChannelDeviceTreeItem
|
||||||
|
{
|
||||||
|
ChannelDevicePluginType = pluginType,
|
||||||
|
DeviceRuntimeId = deviceRuntimeId,
|
||||||
|
ChannelRuntimeId = channelRuntimeId,
|
||||||
|
PluginName = pluginName,
|
||||||
|
PluginType = parsedPluginType
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,148 @@
|
|||||||
|
using Microsoft.CSharp.RuntimeBinder;
|
||||||
|
|
||||||
|
using System.Dynamic;
|
||||||
|
using System.Linq.Expressions;
|
||||||
|
|
||||||
|
using ThingsGateway.Common.Extension;
|
||||||
|
using ThingsGateway.NewLife.Caching;
|
||||||
|
using ThingsGateway.NewLife.Json.Extension;
|
||||||
|
namespace ThingsGateway.Gateway.Razor;
|
||||||
|
|
||||||
|
public static class VariableModelUtils
|
||||||
|
{
|
||||||
|
static MemoryCache MemoryCache = new();
|
||||||
|
public static object GetPropertyValue(VariableRuntime model, string fieldName)
|
||||||
|
{
|
||||||
|
if (model == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(model));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (MemoryCache.TryGetValue(fieldName, out Func<VariableRuntime, object> data))
|
||||||
|
{
|
||||||
|
return data(model);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var ret = MemoryCache.GetOrAdd(fieldName, (fieldName) =>
|
||||||
|
{
|
||||||
|
return GetPropertyValueLambda<VariableRuntime, object?>(fieldName).Compile();
|
||||||
|
})(model);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// 获取属性方法 Lambda 表达式
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TModel"></typeparam>
|
||||||
|
/// <typeparam name="TResult"></typeparam>
|
||||||
|
/// <param name="propertyName"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static Expression<Func<TModel, TResult>> GetPropertyValueLambda<TModel, TResult>(string propertyName) where TModel : class, new()
|
||||||
|
{
|
||||||
|
|
||||||
|
var type = typeof(TModel);
|
||||||
|
var parameter = Expression.Parameter(typeof(TModel));
|
||||||
|
|
||||||
|
return !type.Assembly.IsDynamic && propertyName.Contains('.')
|
||||||
|
? GetComplexPropertyExpression()
|
||||||
|
: GetSimplePropertyExpression();
|
||||||
|
|
||||||
|
Expression<Func<TModel, TResult>> GetSimplePropertyExpression()
|
||||||
|
{
|
||||||
|
Expression body;
|
||||||
|
var p = type.GetPropertyByName(propertyName);
|
||||||
|
if (p != null)
|
||||||
|
{
|
||||||
|
body = Expression.Property(Expression.Convert(parameter, type), p);
|
||||||
|
}
|
||||||
|
else if (type.IsAssignableTo(typeof(IDynamicMetaObjectProvider)))
|
||||||
|
{
|
||||||
|
var binder = Microsoft.CSharp.RuntimeBinder.Binder.GetMember(
|
||||||
|
CSharpBinderFlags.None,
|
||||||
|
propertyName,
|
||||||
|
type,
|
||||||
|
[CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null)]);
|
||||||
|
body = Expression.Dynamic(binder, typeof(object), parameter);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException($"类型 {type.Name} 未找到 {propertyName} 属性,无法获取其值");
|
||||||
|
}
|
||||||
|
|
||||||
|
return Expression.Lambda<Func<TModel, TResult>>(Expression.Convert(body, typeof(TResult)), parameter);
|
||||||
|
}
|
||||||
|
|
||||||
|
Expression<Func<TModel, TResult>> GetComplexPropertyExpression()
|
||||||
|
{
|
||||||
|
var propertyNames = propertyName.Split(".");
|
||||||
|
Expression? body = null;
|
||||||
|
Type t = type;
|
||||||
|
object? propertyInstance = new TModel();
|
||||||
|
foreach (var name in propertyNames)
|
||||||
|
{
|
||||||
|
var p = t.GetPropertyByName(name) ?? throw new InvalidOperationException($"类型 {type.Name} 未找到 {name} 属性,无法获取其值");
|
||||||
|
propertyInstance = p.GetValue(propertyInstance);
|
||||||
|
if (propertyInstance != null)
|
||||||
|
{
|
||||||
|
t = propertyInstance.GetType();
|
||||||
|
}
|
||||||
|
|
||||||
|
body = Expression.Property(body ?? Expression.Convert(parameter, type), p);
|
||||||
|
}
|
||||||
|
return Expression.Lambda<Func<TModel, TResult>>(Expression.Convert(body!, typeof(TResult)), parameter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string GetValue(VariableRuntime row, string fieldName)
|
||||||
|
{
|
||||||
|
switch (fieldName)
|
||||||
|
{
|
||||||
|
case nameof(VariableRuntime.Value):
|
||||||
|
return row.Value?.ToSystemTextJsonString(false) ?? string.Empty;
|
||||||
|
case nameof(VariableRuntime.RawValue):
|
||||||
|
return row.RawValue?.ToSystemTextJsonString(false) ?? string.Empty;
|
||||||
|
case nameof(VariableRuntime.LastSetValue):
|
||||||
|
return row.LastSetValue?.ToSystemTextJsonString(false) ?? string.Empty;
|
||||||
|
case nameof(VariableRuntime.ChangeTime):
|
||||||
|
return row.ChangeTime.ToString("dd-HH:mm:ss.fff");
|
||||||
|
|
||||||
|
case nameof(VariableRuntime.CollectTime):
|
||||||
|
return row.CollectTime.ToString("dd-HH:mm:ss.fff");
|
||||||
|
|
||||||
|
case nameof(VariableRuntime.IsOnline):
|
||||||
|
return row.IsOnline.ToString();
|
||||||
|
|
||||||
|
case nameof(VariableRuntime.LastErrorMessage):
|
||||||
|
return row.LastErrorMessage;
|
||||||
|
|
||||||
|
|
||||||
|
case nameof(VariableRuntime.RuntimeType):
|
||||||
|
return row.RuntimeType;
|
||||||
|
default:
|
||||||
|
|
||||||
|
var ret = VariableModelUtils.GetPropertyValue(row, fieldName);
|
||||||
|
|
||||||
|
if (ret != null)
|
||||||
|
{
|
||||||
|
var t = ret.GetType();
|
||||||
|
if (t.IsEnum)
|
||||||
|
{
|
||||||
|
// 如果是枚举这里返回 枚举的描述信息
|
||||||
|
var itemName = ret.ToString();
|
||||||
|
if (!string.IsNullOrEmpty(itemName))
|
||||||
|
{
|
||||||
|
ret = Utility.GetDisplayName(t, itemName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ret is string str ? str : ret?.ToString() ?? string.Empty;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static Alignment GetAlign(this ITableColumn col) => col.Align ?? Alignment.None;
|
||||||
|
internal static bool GetTextWrap(this ITableColumn col) => col.TextWrap ?? false;
|
||||||
|
internal static bool GetShowTips(this ITableColumn col) => col.ShowTips ?? false;
|
||||||
|
|
||||||
|
internal static bool GetTextEllipsis(this ITableColumn col) => col.TextEllipsis ?? false;
|
||||||
|
}
|
||||||
@@ -9,11 +9,18 @@
|
|||||||
|
|
||||||
@foreach (var col in RowContent.Columns)
|
@foreach (var col in RowContent.Columns)
|
||||||
{
|
{
|
||||||
<td class="@GetFixedCellClassString(col)" style="@GetFixedCellStyleString(col)">
|
<td class="@GetFixedCellClassString(col)" style="@GetFixedCellStyleString(col)" @key=col>
|
||||||
<DynamicElement TagName="div" TriggerClick="@false"
|
<div class="@GetCellClassString(col, false, false)" @key=col>
|
||||||
StopPropagation="false"
|
@if(col.GetShowTips())
|
||||||
class="@GetCellClassString(col, false, false)">
|
{
|
||||||
@GetValue(col, RowContent.Row)
|
<Tooltip @key=col Title="@VariableModelUtils.GetValue(RowContent.Row, col.GetFieldName())" class="text-truncate d-block">
|
||||||
</DynamicElement>
|
@VariableModelUtils.GetValue(RowContent.Row, col.GetFieldName())
|
||||||
|
</Tooltip>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
@VariableModelUtils.GetValue(RowContent.Row, col.GetFieldName())
|
||||||
|
}
|
||||||
|
</div>
|
||||||
</td>
|
</td>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,40 +10,36 @@
|
|||||||
|
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
|
|
||||||
using ThingsGateway.NewLife.Threading;
|
|
||||||
|
|
||||||
using TouchSocket.Core;
|
|
||||||
|
|
||||||
namespace ThingsGateway.Gateway.Razor;
|
namespace ThingsGateway.Gateway.Razor;
|
||||||
|
|
||||||
public partial class VariableRow : IDisposable
|
public partial class VariableRow
|
||||||
{
|
{
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public TableRowContext<VariableRuntime>? RowContent { get; set; }
|
public TableRowContext<VariableRuntime>? RowContent { get; set; }
|
||||||
private bool Disposed;
|
//private bool Disposed;
|
||||||
public void Dispose()
|
//public void Dispose()
|
||||||
{
|
//{
|
||||||
Disposed = true;
|
// Disposed = true;
|
||||||
timer?.SafeDispose();
|
// timer?.SafeDispose();
|
||||||
GC.SuppressFinalize(this);
|
// GC.SuppressFinalize(this);
|
||||||
}
|
//}
|
||||||
TimerX? timer;
|
//TimerX? timer;
|
||||||
protected override void OnAfterRender(bool firstRender)
|
//protected override void OnAfterRender(bool firstRender)
|
||||||
{
|
//{
|
||||||
if (firstRender)
|
// if (firstRender)
|
||||||
{
|
// {
|
||||||
timer = new TimerX(Refresh, null, 1000, 1000, "VariableRow");
|
// timer = new TimerX(Refresh, null, 1000, 1000, "VariableRow");
|
||||||
}
|
// }
|
||||||
base.OnAfterRender(firstRender);
|
// base.OnAfterRender(firstRender);
|
||||||
}
|
//}
|
||||||
|
|
||||||
private Task Refresh(object? state)
|
//private Task Refresh(object? state)
|
||||||
{
|
//{
|
||||||
if (!Disposed)
|
// if (!Disposed)
|
||||||
return InvokeAsync(StateHasChanged);
|
// return InvokeAsync(StateHasChanged);
|
||||||
else
|
// else
|
||||||
return Task.CompletedTask;
|
// return Task.CompletedTask;
|
||||||
}
|
//}
|
||||||
|
|
||||||
protected override void OnParametersSet()
|
protected override void OnParametersSet()
|
||||||
{
|
{
|
||||||
@@ -51,32 +47,6 @@ public partial class VariableRow : IDisposable
|
|||||||
CellClassStringCache?.Clear();
|
CellClassStringCache?.Clear();
|
||||||
base.OnParametersSet();
|
base.OnParametersSet();
|
||||||
}
|
}
|
||||||
/// <summary>
|
|
||||||
/// 获得 指定单元格数据方法
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="col"></param>
|
|
||||||
/// <param name="item"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
protected static RenderFragment GetValue(ITableColumn col, VariableRuntime item) => builder =>
|
|
||||||
{
|
|
||||||
if (col.Template != null)
|
|
||||||
{
|
|
||||||
builder.AddContent(0, col.Template(item));
|
|
||||||
}
|
|
||||||
else if (col.ComponentType == typeof(ColorPicker))
|
|
||||||
{
|
|
||||||
// 自动化处理 ColorPicker 组件
|
|
||||||
builder.AddContent(10, col.RenderColor(item));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
builder.AddContent(20, col.RenderValue(item));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// internal static string? GetDoubleClickCellClassString(bool trigger) => CssBuilder.Default()
|
|
||||||
//.AddClass("is-dbcell", trigger)
|
|
||||||
//.Build();
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 获得指定列头固定列样式
|
/// 获得指定列头固定列样式
|
||||||
|
|||||||
@@ -1,60 +0,0 @@
|
|||||||
//------------------------------------------------------------------------------
|
|
||||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
|
||||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
|
||||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
|
||||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
|
||||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
|
||||||
// 使用文档:https://thingsgateway.cn/
|
|
||||||
// QQ群:605534569
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
namespace ThingsGateway.Gateway.Razor;
|
|
||||||
|
|
||||||
internal static class VariableRowHelpers
|
|
||||||
{
|
|
||||||
internal static Alignment GetAlign(this ITableColumn col) => col.Align ?? Alignment.None;
|
|
||||||
internal static bool GetTextWrap(this ITableColumn col) => col.TextWrap ?? false;
|
|
||||||
internal static bool GetShowTips(this ITableColumn col) => col.ShowTips ?? false;
|
|
||||||
|
|
||||||
|
|
||||||
internal static RenderFragment RenderColor<TItem>(this ITableColumn col, TItem item) => builder =>
|
|
||||||
{
|
|
||||||
var val = GetItemValue(col, item);
|
|
||||||
var v = val?.ToString() ?? "#000";
|
|
||||||
var style = $"background-color: {v};";
|
|
||||||
builder.OpenElement(0, "div");
|
|
||||||
builder.AddAttribute(1, "class", "is-color");
|
|
||||||
builder.AddAttribute(2, "style", style);
|
|
||||||
builder.CloseElement();
|
|
||||||
};
|
|
||||||
internal static object? GetItemValue<TItem>(this ITableColumn col, TItem item)
|
|
||||||
{
|
|
||||||
var fieldName = col.GetFieldName();
|
|
||||||
object? ret;
|
|
||||||
if (item is IDynamicObject dynamicObject)
|
|
||||||
{
|
|
||||||
ret = dynamicObject.GetValue(fieldName);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
ret = Utility.GetPropertyValue<TItem, object?>(item, fieldName);
|
|
||||||
|
|
||||||
if (ret != null)
|
|
||||||
{
|
|
||||||
var t = ret.GetType();
|
|
||||||
if (t.IsEnum)
|
|
||||||
{
|
|
||||||
// 如果是枚举这里返回 枚举的描述信息
|
|
||||||
var itemName = ret.ToString();
|
|
||||||
if (!string.IsNullOrEmpty(itemName))
|
|
||||||
{
|
|
||||||
ret = Utility.GetDisplayName(t, itemName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
internal static bool GetTextEllipsis(this ITableColumn col) => col.TextEllipsis ?? false;
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -4,10 +4,16 @@
|
|||||||
@using ThingsGateway.Admin.Application
|
@using ThingsGateway.Admin.Application
|
||||||
@using ThingsGateway.Admin.Razor
|
@using ThingsGateway.Admin.Razor
|
||||||
@using ThingsGateway.Gateway.Application
|
@using ThingsGateway.Gateway.Application
|
||||||
@inherits ComponentDefault
|
@attribute [JSModuleAutoLoader("Pages/GatewayMonitorPage/Variable/VariableRuntimeInfo.razor.js", AutoInvokeInit = true, AutoInvokeDispose = false, JSObjectReference = true)]
|
||||||
|
@inherits ThingsGatewayModuleComponentBase
|
||||||
|
|
||||||
<AdminTable @ref=table BeforeShowEditDialogCallback="BeforeShowEditDialogCallback"
|
@* RenderMode="TableRenderMode.Table"
|
||||||
|
ShowCardView=false *@
|
||||||
|
|
||||||
|
<AdminTable Id=@Id @ref=table BeforeShowEditDialogCallback="BeforeShowEditDialogCallback"
|
||||||
TItem="VariableRuntime"
|
TItem="VariableRuntime"
|
||||||
|
RenderMode="TableRenderMode.Table"
|
||||||
|
ShowCardView=false
|
||||||
EditDialogSize="Size.ExtraLarge"
|
EditDialogSize="Size.ExtraLarge"
|
||||||
AutoGenerateColumns="true"
|
AutoGenerateColumns="true"
|
||||||
ShowAdvancedSearch=false
|
ShowAdvancedSearch=false
|
||||||
@@ -15,13 +21,13 @@
|
|||||||
AllowResizing="true"
|
AllowResizing="true"
|
||||||
OnAdd="OnAdd"
|
OnAdd="OnAdd"
|
||||||
IsFixedHeader=true
|
IsFixedHeader=true
|
||||||
ShowCardView=false
|
|
||||||
IsMultipleSelect=true
|
IsMultipleSelect=true
|
||||||
SearchMode=SearchMode.Top
|
SearchMode=SearchMode.Top
|
||||||
ShowExtendButtons=true
|
ShowExtendButtons=true
|
||||||
ShowToolbar="true"
|
ShowToolbar="true"
|
||||||
ShowExportButton
|
ShowExportButton
|
||||||
RenderMode="TableRenderMode.Table"
|
OnColumnCreating="OnColumnCreating"
|
||||||
|
OnColumnVisibleChanged=OnColumnVisibleChanged
|
||||||
ShowDefaultButtons=true
|
ShowDefaultButtons=true
|
||||||
ShowSearch=false
|
ShowSearch=false
|
||||||
ExtendButtonColumnWidth=220
|
ExtendButtonColumnWidth=220
|
||||||
@@ -40,8 +46,8 @@
|
|||||||
<TableColumn @bind-Field="@context.RpcWriteCheck" ShowTips=true Filterable=true Sortable=true Visible=false />
|
<TableColumn @bind-Field="@context.RpcWriteCheck" ShowTips=true Filterable=true Sortable=true Visible=false />
|
||||||
|
|
||||||
<TableColumn @bind-Field="@context.Enable" Filterable=true Sortable=true Visible="false" />
|
<TableColumn @bind-Field="@context.Enable" Filterable=true Sortable=true Visible="false" />
|
||||||
<TableColumn Field="@context.ChangeTime" ShowTips=true FieldExpression=@(() => context.ChangeTime) Filterable=true Sortable=true Visible=false />
|
<TableColumn Field="@context.ChangeTime" Width=120 ShowTips=true FieldExpression=@(() => context.ChangeTime) Filterable=true Sortable=true Visible=false />
|
||||||
<TableColumn Field="@context.CollectTime" ShowTips=true FieldExpression=@(() => context.CollectTime) Filterable=true Sortable=true Visible=true />
|
<TableColumn Field="@context.CollectTime" Width=120 ShowTips=true FieldExpression=@(() => context.CollectTime) Filterable=true Sortable=true Visible=true />
|
||||||
<TableColumn Field="@context.IsOnline" FieldExpression=@(() => context.IsOnline) Filterable=true Sortable=true Visible=true />
|
<TableColumn Field="@context.IsOnline" FieldExpression=@(() => context.IsOnline) Filterable=true Sortable=true Visible=true />
|
||||||
<TableColumn Field="@context.LastErrorMessage" ShowTips=true FieldExpression=@(() => context.LastErrorMessage) Filterable=true Sortable=true Visible=false />
|
<TableColumn Field="@context.LastErrorMessage" ShowTips=true FieldExpression=@(() => context.LastErrorMessage) Filterable=true Sortable=true Visible=false />
|
||||||
|
|
||||||
@@ -87,7 +93,24 @@
|
|||||||
|
|
||||||
<RowContentTemplate Context="context">
|
<RowContentTemplate Context="context">
|
||||||
|
|
||||||
<VariableRow RowContent="@context" ColumnsFunc="ColumnsFunc"></VariableRow>
|
|
||||||
|
@foreach (var col in context.Columns)
|
||||||
|
{
|
||||||
|
<td class="@GetFixedCellClassString(col, context)" style="@GetFixedCellStyleString(col, context)" @key=col>
|
||||||
|
<div class="@GetCellClassString(col, false, false)" @key=col>
|
||||||
|
@if (col.GetShowTips())
|
||||||
|
{
|
||||||
|
<Tooltip @key=col Title="@VariableModelUtils.GetValue(context.Row, col.GetFieldName())" class="text-truncate d-block">
|
||||||
|
@VariableModelUtils.GetValue(context.Row, col.GetFieldName())
|
||||||
|
</Tooltip>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
@VariableModelUtils.GetValue(context.Row, col.GetFieldName())
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
}
|
||||||
|
|
||||||
</RowContentTemplate>
|
</RowContentTemplate>
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
|
|
||||||
using Microsoft.AspNetCore.Components.Forms;
|
using Microsoft.AspNetCore.Components.Forms;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
|
using Microsoft.JSInterop;
|
||||||
|
|
||||||
using ThingsGateway.Admin.Application;
|
using ThingsGateway.Admin.Application;
|
||||||
using ThingsGateway.Admin.Razor;
|
using ThingsGateway.Admin.Razor;
|
||||||
@@ -19,11 +20,262 @@ using ThingsGateway.NewLife.Json.Extension;
|
|||||||
|
|
||||||
namespace ThingsGateway.Gateway.Razor;
|
namespace ThingsGateway.Gateway.Razor;
|
||||||
|
|
||||||
public partial class VariableRuntimeInfo : IDisposable
|
public partial class VariableRuntimeInfo
|
||||||
{
|
{
|
||||||
public List<ITableColumn> ColumnsFunc()
|
|
||||||
|
[Inject]
|
||||||
|
[NotNull]
|
||||||
|
protected BlazorAppContext? AppContext { get; set; }
|
||||||
|
|
||||||
|
[Inject]
|
||||||
|
[NotNull]
|
||||||
|
private NavigationManager? NavigationManager { get; set; }
|
||||||
|
|
||||||
|
public string RouteName => NavigationManager.ToBaseRelativePath(NavigationManager.Uri);
|
||||||
|
|
||||||
|
protected bool AuthorizeButton(string operate)
|
||||||
{
|
{
|
||||||
return table?.Columns;
|
return AppContext.IsHasButtonWithRole(RouteName, operate);
|
||||||
|
}
|
||||||
|
[Inject]
|
||||||
|
[NotNull]
|
||||||
|
public IStringLocalizer<ThingsGateway.Razor._Imports>? RazorLocalizer { get; set; }
|
||||||
|
|
||||||
|
[Inject]
|
||||||
|
[NotNull]
|
||||||
|
public IStringLocalizer<ThingsGateway.Admin.Razor._Imports>? AdminLocalizer { get; set; }
|
||||||
|
|
||||||
|
[Inject]
|
||||||
|
[NotNull]
|
||||||
|
public DialogService? DialogService { get; set; }
|
||||||
|
|
||||||
|
[NotNull]
|
||||||
|
public IStringLocalizer? Localizer { get; set; }
|
||||||
|
|
||||||
|
[Inject]
|
||||||
|
[NotNull]
|
||||||
|
public IStringLocalizer<OperDescAttribute>? OperDescLocalizer { get; set; }
|
||||||
|
|
||||||
|
[Inject]
|
||||||
|
[NotNull]
|
||||||
|
public ToastService? ToastService { get; set; }
|
||||||
|
|
||||||
|
#region row
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获得指定列头固定列样式
|
||||||
|
/// </summary>
|
||||||
|
protected string? GetFixedCellStyleString(ITableColumn col, TableRowContext<VariableRuntime> row, int margin = 0)
|
||||||
|
{
|
||||||
|
string? ret = null;
|
||||||
|
if (col.Fixed)
|
||||||
|
{
|
||||||
|
ret = IsTail(col,row) ? GetRightStyle(col,row, margin) : GetLeftStyle(col, row);
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
private string? GetLeftStyle(ITableColumn col, TableRowContext<VariableRuntime> row)
|
||||||
|
{
|
||||||
|
var columns = row.Columns.ToList();
|
||||||
|
var defaultWidth = 200;
|
||||||
|
var width = 0;
|
||||||
|
var start = 0;
|
||||||
|
var index = columns.IndexOf(col);
|
||||||
|
//if (GetFixedDetailRowHeaderColumn)
|
||||||
|
//{
|
||||||
|
// width += DetailColumnWidth;
|
||||||
|
//}
|
||||||
|
//if (GetFixedMultipleSelectColumn)
|
||||||
|
//{
|
||||||
|
// width += MultiColumnWidth;
|
||||||
|
//}
|
||||||
|
if (GetFixedLineNoColumn)
|
||||||
|
{
|
||||||
|
width += LineNoColumnWidth;
|
||||||
|
}
|
||||||
|
while (index > start)
|
||||||
|
{
|
||||||
|
var column = columns[start++];
|
||||||
|
width += column.Width ?? defaultWidth;
|
||||||
|
}
|
||||||
|
return $"left: {width}px;";
|
||||||
|
}
|
||||||
|
private bool GetFixedLineNoColumn = false;
|
||||||
|
|
||||||
|
private string? GetRightStyle(ITableColumn col, TableRowContext<VariableRuntime> row, int margin)
|
||||||
|
{
|
||||||
|
var columns = row.Columns.ToList();
|
||||||
|
var defaultWidth = 200;
|
||||||
|
var width = 0;
|
||||||
|
var index = columns.IndexOf(col);
|
||||||
|
|
||||||
|
// after
|
||||||
|
while (index + 1 < columns.Count)
|
||||||
|
{
|
||||||
|
var column = columns[index++];
|
||||||
|
width += column.Width ?? defaultWidth;
|
||||||
|
}
|
||||||
|
//if (ShowExtendButtons && FixedExtendButtonsColumn)
|
||||||
|
{
|
||||||
|
width += ExtendButtonColumnWidth;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果是固定表头时增加滚动条位置
|
||||||
|
if (IsFixedHeader && (index + 1) == columns.Count)
|
||||||
|
{
|
||||||
|
width += margin;
|
||||||
|
}
|
||||||
|
return $"right: {width}px;";
|
||||||
|
}
|
||||||
|
private bool IsFixedHeader = true;
|
||||||
|
|
||||||
|
public int LineNoColumnWidth { get; set; } = 60;
|
||||||
|
public int ExtendButtonColumnWidth { get; set; } = 220;
|
||||||
|
|
||||||
|
|
||||||
|
private bool IsTail(ITableColumn col,TableRowContext<VariableRuntime> row)
|
||||||
|
{
|
||||||
|
var middle = Math.Floor(row.Columns.Count() * 1.0 / 2);
|
||||||
|
var index = Columns.IndexOf(col);
|
||||||
|
return middle < index;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获得 Cell 文字样式
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="col"></param>
|
||||||
|
/// <param name="hasChildren"></param>
|
||||||
|
/// <param name="inCell"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
protected string? GetCellClassString(ITableColumn col, bool hasChildren, bool inCell)
|
||||||
|
{
|
||||||
|
|
||||||
|
bool trigger = false;
|
||||||
|
return CssBuilder.Default("table-cell")
|
||||||
|
.AddClass(col.GetAlign().ToDescriptionString(), col.Align == Alignment.Center || col.Align == Alignment.Right)
|
||||||
|
.AddClass("is-wrap", col.GetTextWrap())
|
||||||
|
.AddClass("is-ellips", col.GetTextEllipsis())
|
||||||
|
.AddClass("is-tips", col.GetShowTips())
|
||||||
|
.AddClass("is-resizable", AllowResizing)
|
||||||
|
.AddClass("is-tree", IsTree && hasChildren)
|
||||||
|
.AddClass("is-incell", inCell)
|
||||||
|
.AddClass("is-dbcell", trigger)
|
||||||
|
.AddClass(col.CssClass)
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool AllowResizing = true;
|
||||||
|
private bool IsTree = false;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获得指定列头固定列样式
|
||||||
|
/// </summary>
|
||||||
|
protected string? GetFixedCellClassString(ITableColumn col, TableRowContext<VariableRuntime> row)
|
||||||
|
{
|
||||||
|
return CssBuilder.Default()
|
||||||
|
.AddClass("fixed", col.Fixed)
|
||||||
|
.AddClass("fixed-right", col.Fixed && IsTail(col,row))
|
||||||
|
.AddClass("fr", IsLastColumn(col, row))
|
||||||
|
.AddClass("fl", IsFirstColumn(col, row))
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<ITableColumn> Columns=> table?.Columns;
|
||||||
|
|
||||||
|
private bool IsLastColumn(ITableColumn col, TableRowContext<VariableRuntime> row)
|
||||||
|
{
|
||||||
|
var ret = false;
|
||||||
|
if (col.Fixed && !IsTail(col, row))
|
||||||
|
{
|
||||||
|
var index = Columns.IndexOf(col) + 1;
|
||||||
|
ret = index < Columns.Count && Columns[index].Fixed == false;
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
}
|
||||||
|
private bool IsFirstColumn(ITableColumn col, TableRowContext<VariableRuntime> row)
|
||||||
|
{
|
||||||
|
|
||||||
|
var ret = false;
|
||||||
|
if (col.Fixed && IsTail(col, row))
|
||||||
|
{
|
||||||
|
// 查找前一列是否固定
|
||||||
|
var index = Columns.IndexOf(col) - 1;
|
||||||
|
if (index > 0)
|
||||||
|
{
|
||||||
|
ret = !Columns[index].Fixed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region js
|
||||||
|
|
||||||
|
protected override Task InvokeInitAsync() => InvokeVoidAsync("init", Id, Interop, new { Method = nameof(TriggerStateChanged) });
|
||||||
|
|
||||||
|
private Task OnColumnVisibleChanged(string name, bool visible)
|
||||||
|
{
|
||||||
|
_cachedFields = table.GetVisibleColumns.ToArray();
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
private Task OnColumnCreating(List<ITableColumn> columns)
|
||||||
|
{
|
||||||
|
foreach (var column in columns)
|
||||||
|
{
|
||||||
|
column.OnCellRender = a =>
|
||||||
|
{
|
||||||
|
a.Class = $"{column.GetFieldName()}";
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ITableColumn[] _cachedFields = Array.Empty<ITableColumn>();
|
||||||
|
|
||||||
|
[JSInvokable]
|
||||||
|
public List<List<string>> TriggerStateChanged()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (table == null) return null;
|
||||||
|
List<List<string>> ret = new();
|
||||||
|
if (_cachedFields.Length == 0) _cachedFields = table.GetVisibleColumns.ToArray();
|
||||||
|
foreach (var row in table.Rows)
|
||||||
|
{
|
||||||
|
var list = new List<string>(_cachedFields.Length);
|
||||||
|
foreach (var col in _cachedFields)
|
||||||
|
{
|
||||||
|
var fieldName = col.GetFieldName();
|
||||||
|
list.Add(VariableModelUtils.GetValue(row, fieldName));
|
||||||
|
}
|
||||||
|
ret.Add(list);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
return new();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
protected override void OnAfterRender(bool firstRender)
|
||||||
|
{
|
||||||
|
if (firstRender || _cachedFields.Length == 0)
|
||||||
|
{
|
||||||
|
_cachedFields = table.GetVisibleColumns.ToArray();
|
||||||
|
}
|
||||||
|
base.OnAfterRender(firstRender);
|
||||||
}
|
}
|
||||||
|
|
||||||
#if !Management
|
#if !Management
|
||||||
@@ -43,17 +295,20 @@ public partial class VariableRuntimeInfo : IDisposable
|
|||||||
[Inject]
|
[Inject]
|
||||||
private IOptions<WebsiteOptions>? WebsiteOption { get; set; }
|
private IOptions<WebsiteOptions>? WebsiteOption { get; set; }
|
||||||
public bool Disposed { get; set; }
|
public bool Disposed { get; set; }
|
||||||
|
protected override async ValueTask DisposeAsync(bool disposing)
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
{
|
||||||
|
|
||||||
Disposed = true;
|
Disposed = true;
|
||||||
VariableRuntimeDispatchService.UnSubscribe(Refresh);
|
VariableRuntimeDispatchService.UnSubscribe(Refresh);
|
||||||
GC.SuppressFinalize(this);
|
|
||||||
|
await Module.InvokeVoidAsync("dispose", Id);
|
||||||
|
|
||||||
|
await base.DisposeAsync(disposing);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnInitialized()
|
protected override void OnInitialized()
|
||||||
{
|
{
|
||||||
|
Localizer = App.CreateLocalizerByType(GetType());
|
||||||
VariableRuntimeDispatchService.Subscribe(Refresh);
|
VariableRuntimeDispatchService.Subscribe(Refresh);
|
||||||
|
|
||||||
scheduler = new SmartTriggerScheduler(Notify, TimeSpan.FromMilliseconds(1000));
|
scheduler = new SmartTriggerScheduler(Notify, TimeSpan.FromMilliseconds(1000));
|
||||||
|
|||||||
@@ -0,0 +1,121 @@
|
|||||||
|
let handlers = {};
|
||||||
|
|
||||||
|
export function init(id, invoke, options) {
|
||||||
|
//function getCellByClass(row, className) {
|
||||||
|
// // 直接用 querySelector 精确查找
|
||||||
|
// return row.querySelector(`td.${className}`);
|
||||||
|
//}
|
||||||
|
var variableHandler = setInterval(async () => {
|
||||||
|
var admintable = document.getElementById(id);
|
||||||
|
if (!admintable) {
|
||||||
|
clearInterval(variableHandler);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var tables = admintable.getElementsByTagName('table');
|
||||||
|
|
||||||
|
if (!tables || tables.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var table = tables[tables.length - 1];
|
||||||
|
|
||||||
|
if (!table) {
|
||||||
|
clearInterval(variableHandler);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var { method } = options;
|
||||||
|
|
||||||
|
if (!invoke) return;
|
||||||
|
var valss = await invoke.invokeMethodAsync(method);
|
||||||
|
if (valss == null) return;
|
||||||
|
for (let rowIndex = 0; rowIndex < valss.length; rowIndex++) {
|
||||||
|
|
||||||
|
const vals = valss[rowIndex];
|
||||||
|
if (vals == null) continue;
|
||||||
|
|
||||||
|
|
||||||
|
var row = table.rows[rowIndex];
|
||||||
|
if (!row) continue;
|
||||||
|
|
||||||
|
|
||||||
|
for (let i = 0; i < vals.length; i++) {
|
||||||
|
|
||||||
|
const cellValue = vals[i];
|
||||||
|
if (cellValue == null) continue;
|
||||||
|
|
||||||
|
//var cell = getCellByClass(row, cellName)
|
||||||
|
var cell = row.cells[i + 2]
|
||||||
|
|
||||||
|
if (!cell) continue;
|
||||||
|
|
||||||
|
// 查找 tooltip span
|
||||||
|
var cellDiv = cell.querySelector('.table-cell');
|
||||||
|
if (cellDiv) {
|
||||||
|
var tooltipSpan = cell.querySelector('.bb-tooltip');
|
||||||
|
if (tooltipSpan) {
|
||||||
|
|
||||||
|
if (tooltipSpan.innerText != cellValue) {
|
||||||
|
|
||||||
|
tooltipSpan.innerText = cellValue ?? ''; // 更新显示文字
|
||||||
|
tooltipSpan.setAttribute('data-bs-original-title', cellValue ?? ''); // 同步 tooltip 提示
|
||||||
|
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (cellDiv.innerText != cellValue) {
|
||||||
|
|
||||||
|
cellDiv.innerText = cellValue ?? '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//// 查找 switch
|
||||||
|
//var switchDiv = cell.querySelector('.switch');
|
||||||
|
//if (switchDiv) {
|
||||||
|
// if (cellValue === true || cellValue === "on" || cellValue === "True" || cellValue === "true") {
|
||||||
|
// switchDiv.classList.add('is-checked');
|
||||||
|
// switchDiv.classList.add('enable');
|
||||||
|
// switchDiv.classList.remove('is-unchecked');
|
||||||
|
// switchDiv.classList.remove('disabled');
|
||||||
|
|
||||||
|
// switchDiv.querySelectorAll('span')[0].classList.add('border-success');
|
||||||
|
// switchDiv.querySelectorAll('span')[0].classList.add('bg-success');
|
||||||
|
|
||||||
|
// } else {
|
||||||
|
// switchDiv.classList.remove('is-checked');
|
||||||
|
// switchDiv.classList.remove('enable');
|
||||||
|
// switchDiv.classList.add('is-unchecked');
|
||||||
|
// switchDiv.classList.add('disabled');
|
||||||
|
|
||||||
|
// switchDiv.querySelectorAll('span')[0].classList.remove('border-success');
|
||||||
|
// switchDiv.querySelectorAll('span')[0].classList.remove('bg-success');
|
||||||
|
// }
|
||||||
|
// continue;
|
||||||
|
//}
|
||||||
|
//// 默认情况(普通单元格)
|
||||||
|
//cell.innerText = cellValue;
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
, 500) //1000ms刷新一次
|
||||||
|
|
||||||
|
handlers[id] = { variableHandler, invoke };
|
||||||
|
|
||||||
|
}
|
||||||
|
export function dispose(id) {
|
||||||
|
const handler = handlers[id];
|
||||||
|
if (handler) {
|
||||||
|
clearInterval(handler.timer);
|
||||||
|
handler.invoke = null;
|
||||||
|
delete handlers[id];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -27,22 +27,22 @@ public class ModbusSlave : DeviceBase, IModbusAddress
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 继电器
|
/// 继电器
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private NonBlockingDictionary<byte, ByteBlock> ModbusServer01ByteBlocks = new();
|
private ConcurrentDictionary<int, ByteBlock> ModbusServer01ByteBlocks = new();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 开关输入
|
/// 开关输入
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private NonBlockingDictionary<byte, ByteBlock> ModbusServer02ByteBlocks = new();
|
private ConcurrentDictionary<int, ByteBlock> ModbusServer02ByteBlocks = new();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 输入寄存器
|
/// 输入寄存器
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private NonBlockingDictionary<byte, ByteBlock> ModbusServer03ByteBlocks = new();
|
private ConcurrentDictionary<int, ByteBlock> ModbusServer03ByteBlocks = new();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 保持寄存器
|
/// 保持寄存器
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private NonBlockingDictionary<byte, ByteBlock> ModbusServer04ByteBlocks = new();
|
private ConcurrentDictionary<int, ByteBlock> ModbusServer04ByteBlocks = new();
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public override void InitChannel(IChannel channel, ILog? deviceLog = null)
|
public override void InitChannel(IChannel channel, ILog? deviceLog = null)
|
||||||
@@ -157,7 +157,7 @@ public class ModbusSlave : DeviceBase, IModbusAddress
|
|||||||
{
|
{
|
||||||
return PackHelper.LoadSourceRead<T>(this, deviceVariables, maxPack, defaultIntervalTime);
|
return PackHelper.LoadSourceRead<T>(this, deviceVariables, maxPack, defaultIntervalTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
protected override Task DisposeAsync(bool disposing)
|
protected override Task DisposeAsync(bool disposing)
|
||||||
{
|
{
|
||||||
@@ -187,8 +187,42 @@ public class ModbusSlave : DeviceBase, IModbusAddress
|
|||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
private void Init(ModbusRequest mAddress)
|
private void Init(ModbusRequest mAddress)
|
||||||
{
|
{
|
||||||
//自动扩容
|
if (ModbusServer01ByteBlocks.ContainsKey(mAddress.Station))
|
||||||
ModbusServer01ByteBlocks.GetOrAdd(mAddress.Station, a =>
|
return;
|
||||||
|
else
|
||||||
|
ModbusServer01ByteBlocks.GetOrAdd(mAddress.Station, a =>
|
||||||
|
{
|
||||||
|
var bytes = new ByteBlock(256,
|
||||||
|
(c) =>
|
||||||
|
{
|
||||||
|
var data = ArrayPool<byte>.Shared.Rent(c);
|
||||||
|
for (int i = 0; i < data.Length; i++)
|
||||||
|
{
|
||||||
|
data[i] = 0;
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
(m) =>
|
||||||
|
{
|
||||||
|
if (MemoryMarshal.TryGetArray((ReadOnlyMemory<byte>)m, out var result))
|
||||||
|
{
|
||||||
|
ArrayPool<byte>.Shared.Return(result.Array);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
bytes.SetLength(256);
|
||||||
|
for (int i = 0; i < bytes.Length; i++)
|
||||||
|
{
|
||||||
|
bytes.WriteByte(0);
|
||||||
|
}
|
||||||
|
bytes.Position = 0;
|
||||||
|
return bytes;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (ModbusServer02ByteBlocks.ContainsKey(mAddress.Station))
|
||||||
|
return;
|
||||||
|
else
|
||||||
|
ModbusServer02ByteBlocks.GetOrAdd(mAddress.Station, a =>
|
||||||
{
|
{
|
||||||
var bytes = new ByteBlock(256,
|
var bytes = new ByteBlock(256,
|
||||||
(c) =>
|
(c) =>
|
||||||
@@ -216,7 +250,11 @@ public class ModbusSlave : DeviceBase, IModbusAddress
|
|||||||
bytes.Position = 0;
|
bytes.Position = 0;
|
||||||
return bytes;
|
return bytes;
|
||||||
});
|
});
|
||||||
ModbusServer02ByteBlocks.GetOrAdd(mAddress.Station, a =>
|
|
||||||
|
if (ModbusServer03ByteBlocks.ContainsKey(mAddress.Station))
|
||||||
|
return;
|
||||||
|
else
|
||||||
|
ModbusServer03ByteBlocks.GetOrAdd(mAddress.Station, a =>
|
||||||
{
|
{
|
||||||
var bytes = new ByteBlock(256,
|
var bytes = new ByteBlock(256,
|
||||||
(c) =>
|
(c) =>
|
||||||
@@ -244,35 +282,11 @@ public class ModbusSlave : DeviceBase, IModbusAddress
|
|||||||
bytes.Position = 0;
|
bytes.Position = 0;
|
||||||
return bytes;
|
return bytes;
|
||||||
});
|
});
|
||||||
ModbusServer03ByteBlocks.GetOrAdd(mAddress.Station, a =>
|
|
||||||
{
|
if (ModbusServer04ByteBlocks.ContainsKey(mAddress.Station))
|
||||||
var bytes = new ByteBlock(256,
|
return;
|
||||||
(c) =>
|
else
|
||||||
{
|
ModbusServer04ByteBlocks.GetOrAdd(mAddress.Station, a =>
|
||||||
var data = ArrayPool<byte>.Shared.Rent(c);
|
|
||||||
for (int i = 0; i < data.Length; i++)
|
|
||||||
{
|
|
||||||
data[i] = 0;
|
|
||||||
}
|
|
||||||
return data;
|
|
||||||
},
|
|
||||||
(m) =>
|
|
||||||
{
|
|
||||||
if (MemoryMarshal.TryGetArray((ReadOnlyMemory<byte>)m, out var result))
|
|
||||||
{
|
|
||||||
ArrayPool<byte>.Shared.Return(result.Array);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
bytes.SetLength(256);
|
|
||||||
for (int i = 0; i < bytes.Length; i++)
|
|
||||||
{
|
|
||||||
bytes.WriteByte(0);
|
|
||||||
}
|
|
||||||
bytes.Position = 0;
|
|
||||||
return bytes;
|
|
||||||
});
|
|
||||||
ModbusServer04ByteBlocks.GetOrAdd(mAddress.Station, a =>
|
|
||||||
{
|
{
|
||||||
var bytes = new ByteBlock(256,
|
var bytes = new ByteBlock(256,
|
||||||
(c) =>
|
(c) =>
|
||||||
@@ -337,10 +351,11 @@ public class ModbusSlave : DeviceBase, IModbusAddress
|
|||||||
}
|
}
|
||||||
Init(mAddress);
|
Init(mAddress);
|
||||||
}
|
}
|
||||||
var ModbusServer01ByteBlock = ModbusServer01ByteBlocks[mAddress.Station];
|
|
||||||
var ModbusServer02ByteBlock = ModbusServer02ByteBlocks[mAddress.Station];
|
ModbusServer01ByteBlocks.TryGetValue(mAddress.Station,out var ModbusServer01ByteBlock);
|
||||||
var ModbusServer03ByteBlock = ModbusServer03ByteBlocks[mAddress.Station];
|
ModbusServer02ByteBlocks.TryGetValue(mAddress.Station,out var ModbusServer02ByteBlock);
|
||||||
var ModbusServer04ByteBlock = ModbusServer04ByteBlocks[mAddress.Station];
|
ModbusServer03ByteBlocks.TryGetValue(mAddress.Station,out var ModbusServer03ByteBlock);
|
||||||
|
ModbusServer04ByteBlocks.TryGetValue(mAddress.Station,out var ModbusServer04ByteBlock);
|
||||||
if (read)
|
if (read)
|
||||||
{
|
{
|
||||||
using (new ReadLock(_lockSlim))
|
using (new ReadLock(_lockSlim))
|
||||||
@@ -516,12 +531,12 @@ public class ModbusSlave : DeviceBase, IModbusAddress
|
|||||||
return new OperResult(ex);
|
return new OperResult(ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
protected override Task ChannelReceived(IClientChannel client, ReceivedDataEventArgs e, bool last)
|
protected override ValueTask ChannelReceived(IClientChannel client, ReceivedDataEventArgs e, bool last)
|
||||||
{
|
{
|
||||||
return HandleChannelReceivedAsync(client, e, last);
|
return HandleChannelReceivedAsync(client, e, last);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task HandleChannelReceivedAsync(IClientChannel client, ReceivedDataEventArgs e, bool last)
|
private async ValueTask HandleChannelReceivedAsync(IClientChannel client, ReceivedDataEventArgs e, bool last)
|
||||||
{
|
{
|
||||||
if (!TryParseRequest(e.RequestInfo, out var modbusRequest, out var sequences, out var modbusRtu))
|
if (!TryParseRequest(e.RequestInfo, out var modbusRequest, out var sequences, out var modbusRtu))
|
||||||
return;
|
return;
|
||||||
|
|||||||
Reference in New Issue
Block a user