Compare commits

...

9 Commits

Author SHA1 Message Date
2248356998 qq.com
42c740fa1b 更新依赖 2025-10-07 22:25:38 +08:00
2248356998 qq.com
556819c90c 10.11.83 2025-09-30 15:25:15 +08:00
2248356998 qq.com
2522333a9c 更新依赖 2025-09-29 23:16:34 +08:00
2248356998 qq.com
bd4ce7c09b 适配net10 2025-09-29 17:47:24 +08:00
2248356998 qq.com
156ed88bd6 10.11.80 2025-09-29 17:09:12 +08:00
2248356998 qq.com
2416226eb0 10.11.78 2025-09-28 17:01:19 +08:00
2248356998 qq.com
976323a716 适配net10 2025-09-28 13:06:39 +08:00
2248356998 qq.com
3c9e397403 10.11.76 2025-09-28 00:33:56 +08:00
2248356998 qq.com
79406ad4a0 更新依赖 2025-09-27 23:59:38 +08:00
77 changed files with 429 additions and 239 deletions

View File

@@ -45,6 +45,7 @@ public class VerificatInfo : PrimaryIdEntity
/// 登录IP
/// </summary>
[AutoGenerateColumn(Filterable = true, Sortable = true, Width = 200)]
[SugarColumn(IsNullable = true)]
public string LoginIp { get; set; }
/// <summary>
@@ -78,5 +79,6 @@ public class VerificatInfo : PrimaryIdEntity
/// 登录设备
/// </summary>
[AutoGenerateColumn(Filterable = true, Sortable = true, Width = 100)]
[SugarColumn(IsNullable = true)]
public string Device { get; set; }
}

View File

@@ -145,7 +145,7 @@ public class AdminOAuthHandler<TOptions>(
var loginEvent = new LoginEvent
{
Ip = appService.RemoteIpAddress,
Device = appService.UserAgent?.Platform,
Device = appService.UserAgent?.Platform ?? "Unknown",
Expire = expire,
SysUser = sysUser,
VerificatId = CommonUtils.GetSingleId()
@@ -156,7 +156,7 @@ public class AdminOAuthHandler<TOptions>(
//生成verificat信息
var verificatInfo = new VerificatInfo
{
Device = loginEvent.Device,
Device = loginEvent.Device ?? "Unknown",
Expire = loginEvent.Expire,
VerificatTimeout = tokenTimeout,
Id = loginEvent.VerificatId,

View File

@@ -26,7 +26,7 @@
"Module": 2,
"Title": "权限管理",
"Code": "System",
"NavLinkMatch": "All",
"NavLinkMatch": "Prefix",
"Category": "MENU",
"Target": "_self",
"Href": null,
@@ -47,7 +47,7 @@
"ParentId": 0,
"Module": 2,
"Title": "系统运维",
"NavLinkMatch": "All",
"NavLinkMatch": "Prefix",
"Code": "System",
"Category": "MENU",
"Target": "_self",

View File

@@ -235,7 +235,7 @@ public class AuthService : IAuthService
var logingEvent = new LoginEvent
{
Ip = _appService.RemoteIpAddress,
Device = _appService.UserAgent?.Platform,
Device = _appService.UserAgent?.Platform ?? "Unknown",
Expire = expire,
SysUser = sysUser,
VerificatId = verificatId
@@ -344,7 +344,7 @@ public class AuthService : IAuthService
//生成verificat信息
var verificatInfo = new VerificatInfo
{
Device = loginEvent.Device,
Device = loginEvent.Device ?? "Unknown",
Expire = loginEvent.Expire,
VerificatTimeout = tokenTimeout,
Id = loginEvent.VerificatId,

View File

@@ -125,13 +125,22 @@ public class BlazorAppContext
var ownMenus = OwnMenus.Where(a => a.Module == CurrentModuleId);
OwnMenuItems = AdminResourceUtil.BuildMenuTrees(ownMenus).ToList();
AllOwnMenuItems = AdminResourceUtil.BuildMenuTrees(OwnMenus).ToList();
OwnSameLevelMenuItems = ownMenus.Where(a => !a.Href.IsNullOrWhiteSpace()).Select(item => new MenuItem()
OwnSameLevelMenuItems = ownMenus.Where(a => !a.Href.IsNullOrWhiteSpace()).Select(item =>
{
Match = item.NavLinkMatch ?? Microsoft.AspNetCore.Components.Routing.NavLinkMatch.All,
Text = item.Title,
Icon = item.Icon,
Url = item.Href,
Target = item.Target.ToString(),
var menu = new MenuItem()
{
Match = item.NavLinkMatch ?? Microsoft.AspNetCore.Components.Routing.NavLinkMatch.Prefix,
Text = item.Title,
Icon = item.Icon,
Url = item.Href,
Target = item.Target.ToString(),
};
if (menu.Url.IsNullOrEmpty())
{
menu.Match = Microsoft.AspNetCore.Components.Routing.NavLinkMatch.Prefix;
}
return menu;
}).ToList();
UserWorkbenchOutputs = AllMenus.Where(it => UserWorkBench.Shortcuts.Contains(it.Id)).ToList();
}

View File

@@ -41,15 +41,22 @@ public static class AdminResourceUtil
return items
.Where(it => it.ParentId == parentId)
.Select((item, index) =>
new MenuItem()
{
var menu = new MenuItem()
{
Match = item.NavLinkMatch ?? Microsoft.AspNetCore.Components.Routing.NavLinkMatch.All,
Match = item.NavLinkMatch ?? Microsoft.AspNetCore.Components.Routing.NavLinkMatch.Prefix,
Text = item.Title,
Icon = item.Icon,
Url = item.Href,
Target = item.Target.ToString(),
Items = BuildMenuTrees(items, item.Id).ToList()
};
if (menu.Url.IsNullOrEmpty())
{
menu.Match = Microsoft.AspNetCore.Components.Routing.NavLinkMatch.Prefix;
}
return menu;
}
);
}

View File

@@ -21,12 +21,12 @@
<link rel="apple-touch-icon" href="favicon.png">
<base href="/" />
<title>ThingsGateway</title>
<link rel="stylesheet" href=@($"_content/BootstrapBlazor.FontAwesome/css/font-awesome.min.css?v={this.GetType().Assembly.GetName().Version}") />
<link rel="stylesheet" href=@($"_content/BootstrapBlazor/css/bootstrap.blazor.bundle.min.css?v={this.GetType().Assembly.GetName().Version}") />
<link rel="stylesheet" href=@($"_content/BootstrapBlazor/css/motronic.min.css?v={this.GetType().Assembly.GetName().Version}") />
<link rel="stylesheet" href=@($"ThingsGateway.AdminServer.styles.css?v={this.GetType().Assembly.GetName().Version}") />
<link rel="stylesheet" href=@($"{WebsiteConst.DefaultResourceUrl}css/site.css?v={this.GetType().Assembly.GetName().Version}") />
<link rel="stylesheet" href=@($"{WebsiteConst.DefaultResourceUrl}css/devui.css?v={this.GetType().Assembly.GetName().Version}") />
<link rel="stylesheet" href=@($"_content/BootstrapBlazor.FontAwesome/css/font-awesome.min.css") />
<link rel="stylesheet" href=@($"_content/BootstrapBlazor/css/bootstrap.blazor.bundle.min.css") />
<link rel="stylesheet" href=@($"_content/BootstrapBlazor/css/motronic.min.css") />
<link rel="stylesheet" href=@($"ThingsGateway.AdminServer.styles.css") />
<link rel="stylesheet" href=@($"{WebsiteConst.DefaultResourceUrl}css/site.css") />
<link rel="stylesheet" href=@($"{WebsiteConst.DefaultResourceUrl}css/devui.css") />
@* <script src=@($"{WebsiteConst.DefaultResourceUrl}js/theme.js") type="module"></script><!-- 初始主题 --> *@
<!-- PWA Manifest -->
@@ -40,8 +40,8 @@
<BlazorReconnector @rendermode="new InteractiveServerRenderMode(false)" />
<script src=@($"_content/BootstrapBlazor/js/bootstrap.blazor.bundle.min.js?v={this.GetType().Assembly.GetName().Version}")></script>
<script src=@($"{WebsiteConst.DefaultResourceUrl}js/localStorageUtil.js?v={this.GetType().Assembly.GetName().Version}")></script>
<script src=@($"_content/BootstrapBlazor/js/bootstrap.blazor.bundle.min.js")></script>
<script src=@($"{WebsiteConst.DefaultResourceUrl}js/localStorageUtil.js")></script>
<script src="_framework/blazor.web.js"></script>
<!-- PWA Service Worker -->
<script type="text/javascript">'serviceWorker' in navigator && navigator.serviceWorker.register('./service-worker.js')</script>

View File

@@ -70,7 +70,7 @@
<Button @onclick="ShowAbout" class="layout-header-bar d-none d-lg-flex px-2" Icon="fa fa-info" Color="Color.None" TooltipText="@Localizer[nameof(About)]" />
}
@* 版本号 *@
<div class="px-1 navbar-header-text d-none d-lg-block">@_versionString</div>
<div class="px-1 navbar-header-text text-nowrap d-none d-lg-block">@_versionString</div>
@* 主题切换 *@
@* <ThemeToggle /> *@

View File

@@ -12,9 +12,9 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="BootstrapBlazor.TableExport" Version="9.2.6" />
<PackageReference Include="BootstrapBlazor.TableExport" Version="9.2.7" />
<PackageReference Include="Yitter.IdGenerator" Version="1.0.14" />
<PackageReference Include="BootstrapBlazor" Version="9.10.3" />
<PackageReference Include="BootstrapBlazor" Version="9.11.0" />
</ItemGroup>
<ItemGroup>

View File

@@ -10,7 +10,6 @@
// ------------------------------------------------------------------------
using System.Security.Cryptography;
using System.Text;
namespace ThingsGateway.DataEncryption;
@@ -36,7 +35,7 @@ public static class PBKDF2Encryption
var salt = new byte[saltSize];
rng.GetBytes(salt);
#if NET10_0_OR_GREATER
var hash = Rfc2898DeriveBytes.Pbkdf2(Encoding.UTF8.GetBytes(text), salt, iterationCount, HashAlgorithmName.SHA256, derivedKeyLength);
var hash = Rfc2898DeriveBytes.Pbkdf2(System.Text.Encoding.UTF8.GetBytes(text), salt, iterationCount, HashAlgorithmName.SHA256, derivedKeyLength);
#else
using var pbkdf2 = new Rfc2898DeriveBytes(text, salt, iterationCount, HashAlgorithmName.SHA256);
var hash = pbkdf2.GetBytes(derivedKeyLength);
@@ -70,10 +69,10 @@ public static class PBKDF2Encryption
return false;
#if NET10_0_OR_GREATER
var computedHash = Rfc2898DeriveBytes.Pbkdf2(Encoding.UTF8.GetBytes(text), saltBytes, iterationCount, HashAlgorithmName.SHA256, derivedKeyLength);
var computedHash = Rfc2898DeriveBytes.Pbkdf2(System.Text.Encoding.UTF8.GetBytes(text), saltBytes, iterationCount, HashAlgorithmName.SHA256, derivedKeyLength);
#else
using var pbkdf2 = new Rfc2898DeriveBytes(text, saltBytes, iterationCount, HashAlgorithmName.SHA256);
var computedHash = pbkdf2.GetBytes(derivedKeyLength);
using var pbkdf2 = new Rfc2898DeriveBytes(text, saltBytes, iterationCount, HashAlgorithmName.SHA256);
var computedHash = pbkdf2.GetBytes(derivedKeyLength);
#endif
return computedHash.SequenceEqual(storedHashBytes);

View File

@@ -204,7 +204,7 @@ internal sealed partial class SchedulerFactory : ISchedulerFactory
}
else
{
_logger.LogWarning("Schedule hosted service preload completed, and a total of <{Count}> schedulers are appended.", _schedulers.Count);
_logger.LogInformation("Schedule hosted service preload completed, and a total of <{Count}> schedulers are appended.", _schedulers.Count);
}
}
}

View File

@@ -29,7 +29,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Swashbuckle.AspNetCore" Version="9.0.4" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="9.0.6" />
<PackageReference Include="System.Text.Encoding.CodePages" Version="$(NET10Version)" />
</ItemGroup>

View File

@@ -6,7 +6,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="BootstrapBlazor.FontAwesome" Version="9.1.1" />
<PackageReference Include="BootstrapBlazor.FontAwesome" Version="9.1.2" />
</ItemGroup>
<ItemGroup>

View File

@@ -28,7 +28,7 @@
<PackageReference Include="Microsoft.Data.Sqlite" Version="7.0.20" />
<!--<PackageReference Include="Microsoft.Data.Sqlite" Version="$(NET10Version)" />-->
<PackageReference Include="MySqlConnector" Version="2.4.0" />
<PackageReference Include="Npgsql" Version="9.0.3" />
<PackageReference Include="Npgsql" Version="9.0.4" />
<PackageReference Include="CsvHelper" Version="33.1.0" />
<PackageReference Include="TDengine.Connector" Version="3.1.9" />
<PackageReference Include="Oracle.ManagedDataAccess.Core" Version="23.9.1" />

View File

@@ -1,9 +1,9 @@
<Project>
<PropertyGroup>
<PluginVersion>10.11.73</PluginVersion>
<ProPluginVersion>10.11.73</ProPluginVersion>
<DefaultVersion>10.11.73</DefaultVersion>
<PluginVersion>10.11.84</PluginVersion>
<ProPluginVersion>10.11.84</ProPluginVersion>
<DefaultVersion>10.11.84</DefaultVersion>
<AuthenticationVersion>10.11.6</AuthenticationVersion>
<SourceGeneratorVersion>10.11.6</SourceGeneratorVersion>
<NET8Version>8.0.20</NET8Version>
@@ -12,7 +12,7 @@
<IsTrimmable>false</IsTrimmable>
<ManagementProPluginVersion>10.11.70</ManagementProPluginVersion>
<ManagementPluginVersion>10.11.70</ManagementPluginVersion>
<TSVersion>4.0.0-beta.57</TSVersion>
<TSVersion>4.0.0-beta.96</TSVersion>
</PropertyGroup>
@@ -73,7 +73,7 @@
<EmbedAllSources>True</EmbedAllSources>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Roslynator.Analyzers" Version="4.14.0">
<PackageReference Include="Roslynator.Analyzers" Version="4.14.1">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>

View File

@@ -8,7 +8,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="CS-Script" Version="4.11.0" />
<PackageReference Include="CS-Script" Version="4.11.2" />
</ItemGroup>
<ItemGroup>

View File

@@ -37,6 +37,7 @@
<EditorItem @bind-Field="@context.DtrEnable" Ignore=@(context.ChannelType != ChannelTypeEnum.SerialPort) />
<EditorItem @bind-Field="@context.RtsEnable" Ignore=@(context.ChannelType != ChannelTypeEnum.SerialPort) />
<EditorItem @bind-Field="@context.StreamAsync" Ignore=@(context.ChannelType != ChannelTypeEnum.SerialPort) />
<EditorItem @bind-Field="@context.Handshake" Ignore=@(context.ChannelType != ChannelTypeEnum.SerialPort) />
<EditorItem @bind-Field="@context.CacheTimeout" Ignore=@(context.ChannelType == ChannelTypeEnum.UdpSession || context.ChannelType == ChannelTypeEnum.Other) />

View File

@@ -43,6 +43,7 @@
"RemoteUrl": "Remote IP Address",
"RtsEnable": "Rts",
"StreamAsync": "StreamAsync",
"Handshake": "Handshake",
"SaveChannel": "Add/Modify Channel",
"StopBits": "Stop Bits"
},
@@ -103,6 +104,7 @@
"RemoteUrl": "RemoteUrl",
"RtsEnable": "RtsEnable",
"StreamAsync": "StreamAsync",
"Handshake": "Handshake",
"StopBits": "StopBits"
}
}

View File

@@ -40,7 +40,8 @@
"PortName": "COM口",
"RemoteUrl": "远程url",
"RtsEnable": "Rts",
"StreamAsync": "串口流读写",
"StreamAsync": "流读写",
"Handshake": "Handshake",
"SaveChannel": "添加/修改通道",
"StopBits": "停止位"
},
@@ -100,7 +101,8 @@
"PortName": "COM口",
"RemoteUrl": "远程url",
"RtsEnable": "Rts",
"StreamAsync": "串口流读写",
"StreamAsync": "流读写",
"Handshake": "串口流读写",
"StopBits": "停止位"
}
}

View File

@@ -69,15 +69,17 @@ namespace ThingsGateway.Foundation
public virtual bool DtrEnable { get; set; } = true;
/// <summary>
/// StreamAsync
/// Handshake
/// </summary>
public virtual bool StreamAsync { get; set; } = false;
public virtual Handshake Handshake { get; set; }
/// <summary>
/// RtsEnable
/// </summary>
public virtual bool RtsEnable { get; set; } = true;
public virtual bool StreamAsync { get; set; } = true;
/// <inheritdoc/>
[MinValue(1)]
public virtual int MaxConcurrentCount { get; set; } = 1;

View File

@@ -9,7 +9,6 @@
//------------------------------------------------------------------------------
using ThingsGateway.Foundation.Extension.String;
using ThingsGateway.NewLife;
using TouchSocket.SerialPorts;
@@ -100,16 +99,36 @@ public static class ChannelOptionsExtensions
if (channelOptions.MaxClientCount > 0)
config.SetMaxCount(channelOptions.MaxClientCount);
config.SetTransportOption(new TouchSocket.Sockets.TransportOption()
// config.SetTransportOption(new TransportOption()
// {
// MaxBufferSize = 1024,
// MinBufferSize = 512,
// SendPipeOptions = new System.IO.Pipelines.PipeOptions(
// minimumSegmentSize: 512,
// pauseWriterThreshold: 1024,
// resumeWriterThreshold: 512,
// useSynchronizationContext: false),
// ReceivePipeOptions = new System.IO.Pipelines.PipeOptions(
//minimumSegmentSize: 512,
// pauseWriterThreshold: 1024,
// resumeWriterThreshold: 512,
// useSynchronizationContext: false),
// });
config.SetTransportOption(a =>
{
SendPipeOptions = new System.IO.Pipelines.PipeOptions(
minimumSegmentSize: 1024,
useSynchronizationContext: false),
ReceivePipeOptions = new System.IO.Pipelines.PipeOptions(
minimumSegmentSize: 1024,
pauseWriterThreshold: 1024 * 1024,
resumeWriterThreshold: 1024 * 512,
useSynchronizationContext: false)
a.MaxBufferSize = 1024;
a.MinBufferSize = 512;
a.SendPipeOptions = new System.IO.Pipelines.PipeOptions(
minimumSegmentSize: 512,
pauseWriterThreshold: 1024,
resumeWriterThreshold: 512,
useSynchronizationContext: false);
a.ReceivePipeOptions = new System.IO.Pipelines.PipeOptions(
minimumSegmentSize: 512,
pauseWriterThreshold: 1024,
resumeWriterThreshold: 512,
useSynchronizationContext: false);
});
switch (channelType)
@@ -141,10 +160,20 @@ public static class ChannelOptionsExtensions
/// <returns></returns>
private static SerialPortChannel GetSerialPort(this TouchSocketConfig config, IChannelOptions channelOptions)
{
var serialPortOption = FastMapper.Mapper<IChannelOptions,SerialPortOption>(channelOptions);
serialPortOption.ThrowIfNull(nameof(SerialPortOption));
channelOptions.ThrowIfNull(nameof(SerialPortOption));
channelOptions.Config = config;
config.SetSerialPortOption(serialPortOption);
config.SetSerialPortOption(options =>
{
options.PortName = channelOptions.PortName;
options.BaudRate = channelOptions.BaudRate;
options.DataBits = channelOptions.DataBits;
options.Parity = channelOptions.Parity;
options.StopBits = channelOptions.StopBits;
options.DtrEnable = channelOptions.DtrEnable;
options.RtsEnable = channelOptions.RtsEnable;
options.Handshake = channelOptions.Handshake;
options.StreamAsync = channelOptions.StreamAsync;
});
//载入配置
SerialPortChannel serialPortChannel = new SerialPortChannel(channelOptions);
return serialPortChannel;

View File

@@ -72,11 +72,9 @@ public interface IChannelOptions
/// </summary>
bool RtsEnable { get; set; }
/// <summary>
/// StreamAsync
/// </summary>
bool StreamAsync { get; set; }
Handshake Handshake { get; set; }
#endregion
/// <summary>
/// 最大并发数量

View File

@@ -182,7 +182,7 @@ public class DeviceSingleStreamDataHandleAdapter<TRequest> : CustomDataHandlingA
Span<byte> span = default;
if (Logger?.LogLevel <= LogLevel.Trace)
{
span = writer.GetSpan(sendMessage.MaxLength);
span = writer.GetSpan(sendMessage.MaxLength);
}
sendMessage.Build(ref writer);

View File

@@ -18,8 +18,6 @@ using ThingsGateway.NewLife;
using ThingsGateway.NewLife.Collections;
using ThingsGateway.NewLife.Extension;
using TouchSocket.SerialPorts;
namespace ThingsGateway.Foundation;
/// <summary>
@@ -350,7 +348,7 @@ public abstract class DeviceBase : AsyncAndSyncDisposableObject, IDevice
}
private Task BeforeSendAsync(IClientChannel channel, CancellationToken token)
private ValueTask BeforeSendAsync(IClientChannel channel, CancellationToken token)
{
SetDataAdapter(channel);
if (AutoConnect && Channel != null && Channel?.Online != true)
@@ -359,13 +357,13 @@ public abstract class DeviceBase : AsyncAndSyncDisposableObject, IDevice
}
else
{
return Task.CompletedTask;
return EasyValueTask.CompletedTask;
}
}
private WaitLock connectWaitLock = new(nameof(DeviceBase));
public async Task ConnectAsync(CancellationToken token)
public async ValueTask ConnectAsync(CancellationToken token)
{
if (AutoConnect && Channel != null && Channel?.Online != true)
{
@@ -535,8 +533,6 @@ public abstract class DeviceBase : AsyncAndSyncDisposableObject, IDevice
int timeout = 3000,
CancellationToken cancellationToken = default)
{
var waitData = clientChannel.WaitHandlePool.GetWaitDataAsync(out var sign);
command.Sign = sign;
WaitLock? waitLock = null;
@@ -556,19 +552,17 @@ public abstract class DeviceBase : AsyncAndSyncDisposableObject, IDevice
if (waitData.Status == WaitDataStatus.Success)
return waitData.CompletedData;
bool timeoutStatus = false;
var reusableTimeout = _reusableTimeouts.Get();
try
{
var cts = reusableTimeout.GetTokenSource(timeout, cancellationToken, Channel.ClosedToken);
await waitData.WaitAsync(cts.Token).ConfigureAwait(false);
}
catch (OperationCanceledException)
{
timeoutStatus = reusableTimeout.TimeoutStatus;
return timeoutStatus
return reusableTimeout.TimeoutStatus
? new MessageBase(new TimeoutException()) { ErrorMessage = $"Timeout, sign: {sign}" }
: new MessageBase(new OperationCanceledException());
}
@@ -579,7 +573,6 @@ public abstract class DeviceBase : AsyncAndSyncDisposableObject, IDevice
finally
{
reusableTimeout.Set();
timeoutStatus = reusableTimeout.TimeoutStatus;
_reusableTimeouts.Return(reusableTimeout);
}
@@ -589,7 +582,7 @@ public abstract class DeviceBase : AsyncAndSyncDisposableObject, IDevice
}
else
{
var operResult = waitData.Check(timeoutStatus);
var operResult = waitData.Check(reusableTimeout.TimeoutStatus);
return new MessageBase(operResult) { ErrorMessage = $"{operResult.ErrorMessage}, sign: {sign}" };
}
}

View File

@@ -467,5 +467,5 @@ public interface IDevice : IDisposable, IDisposableObject, IAsyncDisposable
/// <param name="deviceLog">单独设备日志</param>
void InitChannel(IChannel channel, ILog? deviceLog = null);
ValueTask<OperResult<ReadOnlyMemory<byte>>> ReadAsync(object state, CancellationToken cancellationToken = default);
Task ConnectAsync(CancellationToken token);
ValueTask ConnectAsync(CancellationToken token);
}

View File

@@ -23,6 +23,7 @@
"RemoteUrl": "RemoteUrl",
"RtsEnable": "RtsEnable",
"StreamAsync": "StreamAsync",
"Handshake": "Handshake",
"StopBits": "StopBits"
},
"ThingsGateway.Foundation.ConverterConfig": {

View File

@@ -22,7 +22,8 @@
"PortName": "COM口",
"RemoteUrl": "远程url",
"RtsEnable": "Rts",
"StreamAsync": "串口流读写",
"StreamAsync": "流读写",
"Handshake": "Handshake",
"StopBits": "停止位"
},
"ThingsGateway.Foundation.ConverterConfig": {

View File

@@ -145,6 +145,7 @@ public class TextFileLogger : ThingsGateway.NewLife.Log.TextFileLog, TouchSocket
}
WriteLog(logLevel, source, message, exception);
}
protected override void Dispose(bool disposing)
{
cache.Remove(CacheKey);

View File

@@ -73,7 +73,7 @@ public partial class ThingsGatewayBitConverter : IThingsGatewayBitConverter
get => endianType; set
{
endianType = value;
TouchSocketBitConverter = new TouchSocketBitConverter(endianType);
TouchSocketBitConverter = TouchSocketBitConverter.GetBitConverter(endianType);
}
}
@@ -188,13 +188,13 @@ public partial class ThingsGatewayBitConverter : IThingsGatewayBitConverter
// 更新设备地址为去除附加信息后的地址
registerAddress = sb.ToString();
var converter = (IThingsGatewayBitConverter)FastMapper.Mapper(this, type);
// 如果没有解析出任何附加信息,则直接返回默认的数据转换器
if (bcdFormat == null && stringlength == null && encoding == null && dataFormat == null && wstring == null)
{
//MemoryCache.Set(cacheKey, this!, 3600);
return converter;
return this;
}
var converter = (IThingsGatewayBitConverter)FastMapper.Mapper(this, type);
// 根据默认的数据转换器创建新的数据转换器实例

View File

@@ -18,14 +18,20 @@ public static class JSRuntimeExtensions
{
await jsRuntime.InvokeVoidAsync("BlazorDiagrams.observe", element, reference, element.Id).ConfigureAwait(false);
}
catch (ObjectDisposedException)
catch
{
// Ignore, DotNetObjectReference was likely disposed
}
}
public static async Task UnobserveResizes(this IJSRuntime jsRuntime, ElementReference element)
{
await jsRuntime.InvokeVoidAsync("BlazorDiagrams.unobserve", element, element.Id).ConfigureAwait(false);
try
{
await jsRuntime.InvokeVoidAsync("BlazorDiagrams.unobserve", element, element.Id).ConfigureAwait(false);
}
catch
{
}
}
}

View File

@@ -16,8 +16,6 @@ using ThingsGateway.NewLife.Json.Extension;
using TouchSocket.Core;
using static System.Net.Mime.MediaTypeNames;
namespace ThingsGateway.Gateway.Application;
/// <summary>

View File

@@ -378,6 +378,8 @@ public abstract class DriverBase : AsyncDisposableObject, IDriver
// 记录设备线程已停止的信息
LogMessage?.LogInformation(string.Format(AppResource.DeviceTaskStop, DeviceName));
await Task.Delay(50).ConfigureAwait(false);
// 执行资源释放操作
await this.SafeDisposeAsync().ConfigureAwait(false);

View File

@@ -142,7 +142,14 @@ public class Channel : ChannelOptionsBase, IPrimaryIdEntity, IBaseDataEntity, IB
/// </summary>
[SugarColumn(ColumnDescription = "StreamAsync", IsNullable = true)]
[AutoGenerateColumn(Visible = false, Filterable = true, Sortable = true)]
public override bool StreamAsync { get; set; } = false;
public override bool StreamAsync { get; set; }
/// <summary>
/// Handshake
/// </summary>
[SugarColumn(ColumnDescription = "Handshake", IsNullable = true)]
[AutoGenerateColumn(Visible = false, Filterable = true, Sortable = true)]
public override Handshake Handshake { get; set; }
/// <summary>
/// 缓存超时

View File

@@ -259,7 +259,15 @@ public static class GlobalData
}
return false;
}
public static bool IsRedundantEnable(long deviceId)
{
if (GlobalData.IdDevices.TryGetValue(deviceId, out var deviceRuntime))
{
if (deviceRuntime.RedundantEnable && deviceRuntime.RedundantDeviceId != null)
return true;
}
return false;
}
public static IEnumerable<VariableRuntime> GetEnableVariables()
{
return IdVariables.Where(a => a.Value.DeviceRuntime?.Enable != false && a.Value.DeviceRuntime?.ChannelRuntime?.Enable != false && a.Value?.Enable == true).Select(a => a.Value);

View File

@@ -308,6 +308,7 @@
"RemoteUrl": "RemoteUrl",
"RtsEnable": "RtsEnable",
"StreamAsync": "StreamAsync",
"Handshake": "Handshake",
"SaveChannel": "Add/Modify Channel",
"SortCode": "SortCode",
"StopBits": "StopBits",

View File

@@ -306,7 +306,8 @@
"PortName": "COM口",
"RemoteUrl": "远程url",
"RtsEnable": "Rts",
"StreamAsync": "串口流读写",
"StreamAsync": "流读写",
"Handshake": "Handshake",
"SaveChannel": "添加/修改通道",
"SortCode": "排序",
"StopBits": "停止位",

View File

@@ -84,7 +84,7 @@ public class ChannelRuntime : Channel
[Newtonsoft.Json.JsonIgnore]
[MapperIgnore]
[AutoGenerateColumn(Ignore = true)]
internal ConcurrentDictionary<long, DeviceRuntime>? DeviceRuntimes { get; } = new(Environment.ProcessorCount, 1000);
internal ConcurrentDictionary<long, DeviceRuntime>? DeviceRuntimes { get; } = new();
/// <summary>
/// 设备数量

View File

@@ -251,7 +251,7 @@ public class DeviceRuntime : Device
[Newtonsoft.Json.JsonIgnore]
[MapperIgnore]
[AutoGenerateColumn(Ignore = true)]
internal ConcurrentDictionary<string, VariableRuntime>? VariableRuntimes { get; } = new(Environment.ProcessorCount, 1000);
internal ConcurrentDictionary<string, VariableRuntime>? VariableRuntimes { get; } = new();
/// <summary>
/// 特殊方法变量

View File

@@ -233,7 +233,7 @@
"Code": "System",
"Category": "1",
"Target": "0",
"NavLinkMatch": "1",
"NavLinkMatch": "0",
"CreateTime": "2025-01-14 20:16:18.362",
"CreateUserId": "0",
"IsDelete": "0",
@@ -248,7 +248,7 @@
"Code": "System",
"Category": "1",
"Target": "0",
"NavLinkMatch": "1",
"NavLinkMatch": "0",
"CreateTime": "2025-01-14 20:16:18.362",
"CreateUserId": "0",
"IsDelete": "0",

View File

@@ -177,7 +177,7 @@ public class ChannelRuntimeService : IChannelRuntimeService
{
await GlobalData.ChannelThreadManage.RestartChannelAsync(newChannelRuntimes).ConfigureAwait(false);
await RuntimeServiceHelper.ChangedDriverAsync(_logger).ConfigureAwait(false);
await RuntimeServiceHelper.ChangedDriverAsync(GlobalData.GetAllVariableBusinessDeviceRuntime().Where(a => !newDeviceRuntimes.Contains(a)).ToArray(), _logger).ConfigureAwait(false);
}
return true;
@@ -209,7 +209,7 @@ public class ChannelRuntimeService : IChannelRuntimeService
{
await GlobalData.ChannelThreadManage.RestartChannelAsync(newChannelRuntimes).ConfigureAwait(false);
await RuntimeServiceHelper.ChangedDriverAsync(_logger).ConfigureAwait(false);
await RuntimeServiceHelper.ChangedDriverAsync(GlobalData.GetAllVariableBusinessDeviceRuntime().Where(a => !newDeviceRuntimes.Contains(a)).ToArray(), _logger).ConfigureAwait(false);
}
return true;
@@ -241,7 +241,7 @@ public class ChannelRuntimeService : IChannelRuntimeService
{
await GlobalData.ChannelThreadManage.RestartChannelAsync(newChannelRuntimes).ConfigureAwait(false);
await RuntimeServiceHelper.ChangedDriverAsync(_logger).ConfigureAwait(false);
await RuntimeServiceHelper.ChangedDriverAsync(GlobalData.GetAllVariableBusinessDeviceRuntime().Where(a => !newDeviceRuntimes.Contains(a)).ToArray(), _logger).ConfigureAwait(false);
}
return true;

View File

@@ -110,7 +110,11 @@ internal sealed class ChannelService : BaseService<Channel>, IChannelService
var device = devices.Keys.ToList();
ManageHelper.CheckDeviceCount(device.Count);
foreach (var item in device)
{
item.RedundantEnable = false;
item.RedundantDeviceId = null;
}
await db.Insertable(device).ExecuteCommandAsync().ConfigureAwait(false);
var variable = devices.SelectMany(a => a.Value).ToList();

View File

@@ -205,7 +205,7 @@ public class DeviceRuntimeService : IDeviceRuntimeService
if (restart)
{
await RuntimeServiceHelper.RestartDeviceAsync(newDeviceRuntimes).ConfigureAwait(false);
await RuntimeServiceHelper.ChangedDriverAsync(_logger).ConfigureAwait(false);
await RuntimeServiceHelper.ChangedDriverAsync(GlobalData.GetAllVariableBusinessDeviceRuntime().Where(a => !newDeviceRuntimes.Contains(a)).ToArray(), _logger).ConfigureAwait(false);
}
return true;

View File

@@ -53,7 +53,11 @@ internal sealed class DeviceService : BaseService<Device>, IDeviceService
{
var device = devices.Keys.ToList();
ManageHelper.CheckDeviceCount(device.Count);
foreach (var item in device)
{
item.RedundantEnable = false;
item.RedundantDeviceId = null;
}
await db.Insertable(device).ExecuteCommandAsync().ConfigureAwait(false);
var variable = devices.SelectMany(a => a.Value).ToList();
@@ -263,6 +267,9 @@ internal sealed class DeviceService : BaseService<Device>, IDeviceService
else
ManageHelper.CheckDeviceCount(1);
if (input.RedundantEnable && GlobalData.IsRedundantEnable(input.RedundantDeviceId ?? 0))
throw Oops.Bah($"Redundancy configuration error, backup device has been planned into another redundancy group");
if (await base.SaveAsync(input, type).ConfigureAwait(false))
{

View File

@@ -820,6 +820,7 @@ internal sealed class DeviceThreadManage : IAsyncDisposable, IDeviceThreadManage
LogMessage?.LogInformation(string.Format(AppResource.ChannelDispose, CurrentChannel?.Name ?? string.Empty));
await Task.Delay(50).ConfigureAwait(false);
LogMessage?.Logs?.ForEach(a => a.TryDispose());
}
finally

View File

@@ -239,7 +239,7 @@ internal static class RuntimeServiceHelper
if (group.Key != null)
await group.Key.RestartDeviceAsync(group.Value, false).ConfigureAwait(false);
}
foreach (var group in GlobalData.GetAllVariableBusinessDeviceRuntime().Where(a => a.Driver?.DeviceThreadManage != null).GroupBy(a => a.Driver.DeviceThreadManage))
foreach (var group in GlobalData.GetAllVariableBusinessDeviceRuntime().Where(a => !newDeviceRuntimes.Contains(a)).Where(a => a.Driver?.DeviceThreadManage != null).GroupBy(a => a.Driver.DeviceThreadManage))
{
if (group.Key != null)
await group.Key.RestartDeviceAsync(group.ToArray(), false).ConfigureAwait(false);
@@ -260,7 +260,7 @@ internal static class RuntimeServiceHelper
await group.Key.RemoveDeviceAsync(group.Value.Select(a => a.Id).ToArray()).ConfigureAwait(false);
}
foreach (var group in GlobalData.GetAllVariableBusinessDeviceRuntime().Where(a => a.Driver?.DeviceThreadManage != null).GroupBy(a => a.Driver.DeviceThreadManage))
foreach (var group in GlobalData.GetAllVariableBusinessDeviceRuntime().Where(a => !deviceRuntimes.Contains(a)).Where(a => a.Driver?.DeviceThreadManage != null).GroupBy(a => a.Driver.DeviceThreadManage))
{
if (group.Key != null)
await group.Key.RestartDeviceAsync(group.ToArray(), false).ConfigureAwait(false);
@@ -268,9 +268,9 @@ internal static class RuntimeServiceHelper
}
public static async Task ChangedDriverAsync(ILogger logger)
public static async Task ChangedDriverAsync(DeviceRuntime[] channelDevice, ILogger logger)
{
var channelDevice = GlobalData.GetAllVariableBusinessDeviceRuntime();
await channelDevice.ParallelForEachAsync(async (item, token) =>
{

View File

@@ -398,22 +398,22 @@ public class VariableRuntimeService : IVariableRuntimeService
//批量修改之后,需要重新加载通道
RuntimeServiceHelper.Init(newChannelRuntimes);
{
var newDeviceRuntimes = datas.Item2.AdaptListDeviceRuntime();
RuntimeServiceHelper.Init(newDeviceRuntimes);
}
{
var newVariableRuntimes = datas.Item3.AdaptListVariableRuntime();
RuntimeServiceHelper.Init(newVariableRuntimes);
}
var newDeviceRuntimes = datas.Item2.AdaptListDeviceRuntime();
RuntimeServiceHelper.Init(newDeviceRuntimes);
var newVariableRuntimes = datas.Item3.AdaptListVariableRuntime();
RuntimeServiceHelper.Init(newVariableRuntimes);
//根据条件重启通道线程
if (restart)
{
await GlobalData.ChannelThreadManage.RestartChannelAsync(newChannelRuntimes).ConfigureAwait(false);
await RuntimeServiceHelper.ChangedDriverAsync(_logger).ConfigureAwait(false);
await RuntimeServiceHelper.ChangedDriverAsync(GlobalData.GetAllVariableBusinessDeviceRuntime().Where(a => !newDeviceRuntimes.Contains(a)).ToArray(), _logger).ConfigureAwait(false);
}
}
}
@@ -437,22 +437,21 @@ public class VariableRuntimeService : IVariableRuntimeService
//批量修改之后,需要重新加载通道
RuntimeServiceHelper.Init(newChannelRuntimes);
{
var newDeviceRuntimes = datas.Item2.AdaptListDeviceRuntime();
RuntimeServiceHelper.Init(newDeviceRuntimes);
}
{
var newVariableRuntimes = datas.Item3.AdaptListVariableRuntime();
RuntimeServiceHelper.Init(newVariableRuntimes);
}
var newDeviceRuntimes = datas.Item2.AdaptListDeviceRuntime();
RuntimeServiceHelper.Init(newDeviceRuntimes);
var newVariableRuntimes = datas.Item3.AdaptListVariableRuntime();
RuntimeServiceHelper.Init(newVariableRuntimes);
//根据条件重启通道线程
if (restart)
{
await GlobalData.ChannelThreadManage.RestartChannelAsync(newChannelRuntimes).ConfigureAwait(false);
await RuntimeServiceHelper.ChangedDriverAsync(_logger).ConfigureAwait(false);
await RuntimeServiceHelper.ChangedDriverAsync(GlobalData.GetAllVariableBusinessDeviceRuntime().Where(a => !newDeviceRuntimes.Contains(a)).ToArray(), _logger).ConfigureAwait(false);
}
}
}

View File

@@ -115,6 +115,7 @@
<EditorItem @bind-Field="@context.DtrEnable" Ignore=@(context.ChannelType != ChannelTypeEnum.SerialPort) />
<EditorItem @bind-Field="@context.RtsEnable" Ignore=@(context.ChannelType != ChannelTypeEnum.SerialPort) />
<EditorItem @bind-Field="@context.StreamAsync" Ignore=@(context.ChannelType != ChannelTypeEnum.SerialPort) />
<EditorItem @bind-Field="@context.Handshake" Ignore=@(context.ChannelType != ChannelTypeEnum.SerialPort) />
<EditorItem @bind-Field="@context.CacheTimeout" Ignore=@(context.ChannelType == ChannelTypeEnum.UdpSession || context.ChannelType == ChannelTypeEnum.Other) />

View File

@@ -82,7 +82,8 @@
<EditorItem @bind-Field="@context.StopBits" Ignore=@(context.ChannelType!=ChannelTypeEnum.SerialPort) />
<EditorItem @bind-Field="@context.DtrEnable" Ignore=@(context.ChannelType!=ChannelTypeEnum.SerialPort) />
<EditorItem @bind-Field="@context.RtsEnable" Ignore=@(context.ChannelType!=ChannelTypeEnum.SerialPort) />
<EditorItem @bind-Field="@context.StreamAsync" Ignore=@(context.ChannelType!=ChannelTypeEnum.SerialPort) />
<EditorItem @bind-Field="@context.StreamAsync" Ignore=@(context.ChannelType != ChannelTypeEnum.SerialPort) />
<EditorItem @bind-Field="@context.Handshake" Ignore=@(context.ChannelType!=ChannelTypeEnum.SerialPort) />
<EditorItem @bind-Field="@context.CacheTimeout" Ignore=@(context.ChannelType==ChannelTypeEnum.UdpSession||context.ChannelType==ChannelTypeEnum.Other) />

View File

@@ -1319,17 +1319,26 @@ EventCallback.Factory.Create<MouseEventArgs>(this, async e =>
private TreeViewItem<ChannelDeviceTreeItem> UnknownTreeViewItem;
SmartTriggerScheduler? scheduler;
private bool _initialized;
public override async Task SetParametersAsync(ParameterView parameters)
{
parameters.SetParameterProperties(this);
OnInitialized();
await OnInitializedAsync();
OnParametersSet();
StateHasChanged();
await OnParametersSetAsync();
if (!_initialized)
{
_initialized = true;
OnInitialized();
await OnInitializedAsync();
OnParametersSet();
StateHasChanged();
await OnParametersSetAsync();
}
else
{
OnParametersSet();
StateHasChanged();
await OnParametersSetAsync();
}
}
protected override async Task OnInitializedAsync()
@@ -1402,11 +1411,11 @@ EventCallback.Factory.Create<MouseEventArgs>(this, async e =>
await base.OnInitializedAsync();
}
private async Task Notify(CancellationToken cancellationToken)
{
if (cancellationToken.IsCancellationRequested) return;
if (Disposed) return;
await OnClickSearch(SearchText);
Value = GetValue(Value);
@@ -1415,6 +1424,7 @@ EventCallback.Factory.Create<MouseEventArgs>(this, async e =>
await ChannelDeviceChanged.Invoke(Value);
}
await InvokeAsync(StateHasChanged);
}
private static ChannelDeviceTreeItem GetValue(ChannelDeviceTreeItem channelDeviceTreeItem)

View File

@@ -120,6 +120,7 @@ public partial class DeviceEditComponent
private string ChannelName;
private string DeviceName;
private bool _initialized;
public override async Task SetParametersAsync(ParameterView parameters)
{
if (ChannelName.IsNullOrEmpty())
@@ -127,11 +128,22 @@ public partial class DeviceEditComponent
parameters.SetParameterProperties(this);
ChannelName = await ChannelPageService.GetChannelNameAsync(Model?.ChannelId ?? 0);
DeviceName = await DevicePageService.GetDeviceNameAsync(Model?.RedundantDeviceId ?? 0);
OnInitialized();
await OnInitializedAsync();
OnParametersSet();
StateHasChanged();
await OnParametersSetAsync();
if (!_initialized)
{
_initialized = true;
OnInitialized();
await OnInitializedAsync();
OnParametersSet();
StateHasChanged();
await OnParametersSetAsync();
}
else
{
OnParametersSet();
StateHasChanged();
await OnParametersSetAsync();
}
}
else
{

View File

@@ -49,6 +49,11 @@ public partial class VariableEditComponent
private string DeviceName;
private string ChoiceBusinessDeviceName;
private bool first = false;
private bool _initialized;
public override async Task SetParametersAsync(ParameterView parameters)
{
if (DeviceName.IsNullOrEmpty() && first == false)
@@ -56,14 +61,31 @@ public partial class VariableEditComponent
first = true;
parameters.SetParameterProperties(this);
DeviceName = await DevicePageService.GetDeviceNameAsync(Model?.DeviceId ?? 0);
OnInitialized();
await OnInitializedAsync();
OnParametersSet();
if (!_initialized)
{
_initialized = true;
ChoiceBusinessDeviceId = ChoiceBusinessDeviceId > 0 ? ChoiceBusinessDeviceId : (await DevicePageService.OnDeviceSelectedItemQueryAsync(new VirtualizeQueryOption() { Count = 1 }, false).ConfigureAwait(false)).Items.FirstOrDefault()?.Value?.ToLong() ?? 0;
ChoiceBusinessDeviceName = (await DevicePageService.GetDeviceNameAsync(ChoiceBusinessDeviceId)) ?? string.Empty;
OnInitialized();
await OnInitializedAsync();
OnParametersSet();
ChoiceBusinessDeviceId = ChoiceBusinessDeviceId > 0 ? ChoiceBusinessDeviceId : (await DevicePageService.OnDeviceSelectedItemQueryAsync(new VirtualizeQueryOption() { Count = 1 }, false).ConfigureAwait(false)).Items.FirstOrDefault()?.Value?.ToLong() ?? 0;
ChoiceBusinessDeviceName = (await DevicePageService.GetDeviceNameAsync(ChoiceBusinessDeviceId)) ?? string.Empty;
await InvokeAsync(StateHasChanged);
await OnParametersSetAsync();
}
else
{
OnParametersSet();
ChoiceBusinessDeviceId = ChoiceBusinessDeviceId > 0 ? ChoiceBusinessDeviceId : (await DevicePageService.OnDeviceSelectedItemQueryAsync(new VirtualizeQueryOption() { Count = 1 }, false).ConfigureAwait(false)).Items.FirstOrDefault()?.Value?.ToLong() ?? 0;
ChoiceBusinessDeviceName = (await DevicePageService.GetDeviceNameAsync(ChoiceBusinessDeviceId)) ?? string.Empty;
await InvokeAsync(StateHasChanged);
await OnParametersSetAsync();
}
await InvokeAsync(StateHasChanged);
await OnParametersSetAsync();

View File

@@ -35,7 +35,8 @@ public partial class RulesPage : ThingsGatewayModuleComponentBase
protected override async Task OnAfterRenderAsync(bool firstRender)
{
await base.OnAfterRenderAsync(firstRender);
if (firstRender) {
if (firstRender)
{
StateHasChanged();
}
}

View File

@@ -36,7 +36,7 @@
<Button OnClick="ShowAbout" class="layout-header-bar d-none d-lg-flex px-2" Icon="fa fa-info" Color="Color.None" TooltipText="@Localizer[nameof(About)]" />
}
@* 版本号 *@
<div class="px-1 navbar-header-text d-none d-lg-block">@_versionString</div>
<div class="px-1 navbar-header-text text-nowrap d-none d-lg-block">@_versionString</div>
@* 主题切换 *@
@* <ThemeToggle /> *@

View File

@@ -8,11 +8,8 @@
// QQ群605534569
//------------------------------------------------------------------------------
using BenchmarkConsoleApp;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Diagnosers;
using BenchmarkDotNet.Jobs;
using Longbow.Modbus;
using Longbow.TcpSocket;
@@ -31,11 +28,15 @@ using ModbusMaster = ThingsGateway.Foundation.Modbus.ModbusMaster;
namespace ThingsGateway.Foundation;
[SimpleJob(RuntimeMoniker.Net80)]
//[SimpleJob(RuntimeMoniker.Net80)]
//[SimpleJob(RuntimeMoniker.Net10_0)]
[MemoryDiagnoser]
public class ModbusBenchmark : IDisposable
{
public static int ClientCount = 1;
public static int TaskNumberOfItems = 1;
public static int NumberOfItems = 10;
private readonly List<IModbusClient> _lgbModbusClients = [];
private List<ModbusMaster> thingsgatewaymodbuss = new();
private List<IModbusMaster> nmodbuss = new();
@@ -45,7 +46,7 @@ public class ModbusBenchmark : IDisposable
[GlobalSetup]
public async Task Init()
{
for (int i = 0; i < Program.ClientCount; i++)
for (int i = 0; i < ClientCount; i++)
{
var clientConfig = new TouchSocket.Core.TouchSocketConfig();
@@ -57,23 +58,23 @@ public class ModbusBenchmark : IDisposable
ModbusType = ModbusTypeEnum.ModbusTcp,
};
thingsgatewaymodbus.InitChannel(clientChannel);
await clientChannel.SetupAsync(clientChannel.Config);
await clientChannel.SetupAsync(clientChannel.Config);
clientChannel.Logger.LogLevel = LogLevel.Warning;
await thingsgatewaymodbus.ConnectAsync(CancellationToken.None);
await thingsgatewaymodbus.ReadAsync("40001", 100);
await thingsgatewaymodbus.ConnectAsync(CancellationToken.None);
await thingsgatewaymodbus.ModbusReadAsync(new ModbusAddress() { FunctionCode = 3, StartAddress = 0, Length = 100 });
thingsgatewaymodbuss.Add(thingsgatewaymodbus);
}
for (int i = 0; i < Program.ClientCount; i++)
for (int i = 0; i < ClientCount; i++)
{
var factory = new NModbus.ModbusFactory();
var nmodbus = factory.CreateMaster(new TcpClient("127.0.0.1", 502));
await nmodbus.ReadHoldingRegistersAsync(1, 0, 100);
await nmodbus.ReadHoldingRegistersAsync(1, 0, 100);
nmodbuss.Add(nmodbus);
}
//for (int i = 0; i < Program.ClientCount; i++)
//for (int i = 0; i < ClientCount; i++)
//{
// ModbusTcpNet modbusTcpNet = new();
// modbusTcpNet.IpAddress = "127.0.0.1";
@@ -83,13 +84,13 @@ public class ModbusBenchmark : IDisposable
// modbusTcpNets.Add(modbusTcpNet);
//}
for (int i = 0; i < Program.ClientCount; i++)
for (int i = 0; i < ClientCount; i++)
{
var client = new ModbusTcpMaster();
await client.SetupAsync(new TouchSocketConfig()
.SetRemoteIPHost("127.0.0.1:502"));
await client.ConnectAsync(CancellationToken.None);
await client.ReadHoldingRegistersAsync(0, 100);
await client.SetupAsync(new TouchSocketConfig()
.SetRemoteIPHost("127.0.0.1:502"));
await client.ConnectAsync(CancellationToken.None);
await client.ReadHoldingRegistersAsync(0, 100);
modbusTcpMasters.Add(client);
}
@@ -101,11 +102,11 @@ public class ModbusBenchmark : IDisposable
var provider = sc.BuildServiceProvider();
var factory = provider.GetRequiredService<IModbusFactory>();
for (int i = 0; i < Program.ClientCount; i++)
for (int i = 0; i < ClientCount; i++)
{
var client = factory.GetOrCreateTcpMaster();
await client.ConnectAsync("127.0.0.1", 502);
await client.ReadHoldingRegistersAsync(0x01, 0x00, 10);
await client.ConnectAsync("127.0.0.1", 502);
await client.ReadHoldingRegistersAsync(0x01, 0x00, 10);
_lgbModbusClients.Add(client);
}
@@ -120,11 +121,11 @@ public class ModbusBenchmark : IDisposable
foreach (var thingsgatewaymodbus in thingsgatewaymodbuss)
{
for (int i = 0; i < Program.TaskNumberOfItems; i++)
for (int i = 0; i < TaskNumberOfItems; i++)
{
tasks.Add(Task.Run(async () =>
{
for (int i = 0; i < Program.NumberOfItems; i++)
for (int i = 0; i < NumberOfItems; i++)
{
var result = await thingsgatewaymodbus.ModbusReadAsync(addr).ConfigureAwait(false);
if (!result.IsSuccess)
@@ -143,17 +144,22 @@ public class ModbusBenchmark : IDisposable
public async Task LongbowModbus()
{
List<Task> tasks = new List<Task>();
foreach (var _lgbModbusClient in _lgbModbusClients)
foreach (var client in _lgbModbusClients)
{
for (int i = 0; i < Program.TaskNumberOfItems; i++)
for (int i = 0; i < TaskNumberOfItems; i++)
{
tasks.Add(Task.Run(async () =>
{
for (int i = 0; i < Program.NumberOfItems; i++)
for (int i = 0; i < NumberOfItems; i++)
{
using var cts = new CancellationTokenSource(3000);
var task = await _lgbModbusClient.ReadHoldingRegistersAsync(1, 0, 100, cts.Token).ConfigureAwait(false);
var result = await client.ReadHoldingRegistersAsync(1, 0, 100, cts.Token).ConfigureAwait(false);
var data = result.ReadUShortValues(100);
if (!result.IsSuccess)
{
throw new Exception(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss ffff") + result.Exception);
}
}
}));
}
@@ -167,11 +173,11 @@ public class ModbusBenchmark : IDisposable
List<Task> tasks = new List<Task>();
foreach (var modbusTcpMaster in modbusTcpMasters)
{
for (int i = 0; i < Program.TaskNumberOfItems; i++)
for (int i = 0; i < TaskNumberOfItems; i++)
{
tasks.Add(Task.Run(async () =>
{
for (int i = 0; i < Program.NumberOfItems; i++)
for (int i = 0; i < NumberOfItems; i++)
{
var result = await modbusTcpMaster.ReadHoldingRegistersAsync(0, 100).ConfigureAwait(false);
var data = TouchSocketBitConverter.ConvertValues<byte, ushort>(result.Data.Span, EndianType.Little);
@@ -193,11 +199,11 @@ public class ModbusBenchmark : IDisposable
List<Task> tasks = new List<Task>();
foreach (var nmodbus in nmodbuss)
{
for (int i = 0; i < Program.TaskNumberOfItems; i++)
for (int i = 0; i < TaskNumberOfItems; i++)
{
tasks.Add(Task.Run(async () =>
{
for (int i = 0; i < Program.NumberOfItems; i++)
for (int i = 0; i < NumberOfItems; i++)
{
var result = await nmodbus.ReadHoldingRegistersAsync(1, 0, 100).ConfigureAwait(false);
}
@@ -215,11 +221,11 @@ public class ModbusBenchmark : IDisposable
// List<Task> tasks = new List<Task>();
// foreach (var modbusTcpNet in modbusTcpNets)
// {
// for (int i = 0; i < Program.TaskNumberOfItems; i++)
// for (int i = 0; i < TaskNumberOfItems; i++)
// {
// tasks.Add(Task.Run(async () =>
// {
// for (int i = 0; i < Program.NumberOfItems; i++)
// for (int i = 0; i < NumberOfItems; i++)
// {
// var result = await modbusTcpNet.ReadAsync("0", 100);
// if (!result.IsSuccess)

View File

@@ -8,11 +8,8 @@
// QQ群605534569
//------------------------------------------------------------------------------
using BenchmarkConsoleApp;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Diagnosers;
using BenchmarkDotNet.Jobs;
using HslCommunication.Profinet.Siemens;
@@ -24,11 +21,15 @@ using TouchSocket.Core;
namespace ThingsGateway.Foundation;
[SimpleJob(RuntimeMoniker.Net80)]
//[SimpleJob(RuntimeMoniker.Net80)]
//[SimpleJob(RuntimeMoniker.Net10_0)]
[MemoryDiagnoser]
public class S7Benchmark : IDisposable
{
public static int ClientCount = 5;
public static int TaskNumberOfItems = 1;
public static int NumberOfItems = 5;
private List<SiemensS7Master> siemensS7s = new();
private List<Plc> plcs = new();
@@ -39,7 +40,7 @@ public class S7Benchmark : IDisposable
{
{
for (int i = 0; i < Program.ClientCount; i++)
for (int i = 0; i < ClientCount; i++)
{
var clientConfig = new TouchSocket.Core.TouchSocketConfig();
@@ -56,14 +57,14 @@ public class S7Benchmark : IDisposable
await siemensS7.ReadAsync("M1", 100);
siemensS7s.Add(siemensS7);
}
for (int i = 0; i < Program.ClientCount; i++)
for (int i = 0; i < ClientCount; i++)
{
var siemensS7Net = new SiemensS7Net(SiemensPLCS.S1500, "127.0.0.1");
await siemensS7Net.ConnectServerAsync();
await siemensS7Net.ReadAsync("M0", 100);
siemensS7Nets.Add(siemensS7Net);
}
for (int i = 0; i < Program.ClientCount; i++)
for (int i = 0; i < ClientCount; i++)
{
var plc = new Plc(CpuType.S71500, "127.0.0.1", 102, 0, 0);
await plc.OpenAsync();//打开plc连接
@@ -79,11 +80,11 @@ public class S7Benchmark : IDisposable
List<Task> tasks = new List<Task>();
foreach (var plc in plcs)
{
for (int i = 0; i < Program.TaskNumberOfItems; i++)
for (int i = 0; i < TaskNumberOfItems; i++)
{
tasks.Add(Task.Run(async () =>
{
for (int i = 0; i < Program.NumberOfItems; i++)
for (int i = 0; i < NumberOfItems; i++)
{
var result = await plc.ReadAsync(DataType.Memory, 1, 0, VarType.Byte, 100);
}
@@ -99,11 +100,11 @@ public class S7Benchmark : IDisposable
List<Task> tasks = new List<Task>();
foreach (var siemensS7Net in siemensS7Nets)
{
for (int i = 0; i < Program.TaskNumberOfItems; i++)
for (int i = 0; i < TaskNumberOfItems; i++)
{
tasks.Add(Task.Run(async () =>
{
for (int i = 0; i < Program.NumberOfItems; i++)
for (int i = 0; i < NumberOfItems; i++)
{
var result = await siemensS7Net.ReadAsync("M0", 100);
if (!result.IsSuccess)
@@ -129,11 +130,11 @@ public class S7Benchmark : IDisposable
List<Task> tasks = new List<Task>();
foreach (var siemensS7 in siemensS7s)
{
for (int i = 0; i < Program.TaskNumberOfItems; i++)
for (int i = 0; i < TaskNumberOfItems; i++)
{
tasks.Add(Task.Run(async () =>
{
for (int i = 0; i < Program.NumberOfItems; i++)
for (int i = 0; i < NumberOfItems; i++)
{
var result = await siemensS7.S7ReadAsync(siemensS7Address);
if (!result.IsSuccess)

View File

@@ -20,14 +20,11 @@ namespace BenchmarkConsoleApp
{
internal class Program
{
public static int ClientCount = 50;
public static int TaskNumberOfItems = 6;
public static int NumberOfItems = 50;
private static async Task Main(string[] args)
{
Console.WriteLine("开始测试前请先启动ModbusSlave建议使用本项目自带的ThingsGateway.Debug.Photino软件开启S7可以用KEPSERVER的S7模拟服务");
Console.WriteLine($"多客户端({ClientCount}),多线程({TaskNumberOfItems})并发读取({NumberOfItems})测试,共{ClientCount * TaskNumberOfItems * NumberOfItems}次");
await Task.CompletedTask;
//ModbusBenchmark modbusBenchmark = new ModbusBenchmark();
//System.Diagnostics.Stopwatch stopwatch = new();
@@ -46,15 +43,15 @@ namespace BenchmarkConsoleApp
//.WithOptions(ConfigOptions.DisableOptimizationsValidator)
//);
BenchmarkRunner.Run<MapperBench>(
ManualConfig.Create(DefaultConfig.Instance)
.WithOptions(ConfigOptions.DisableOptimizationsValidator)
);
// BenchmarkRunner.Run<MapperBench>(
//ManualConfig.Create(DefaultConfig.Instance)
//.WithOptions(ConfigOptions.DisableOptimizationsValidator)
//);
// BenchmarkRunner.Run<ModbusBenchmark>(
// ManualConfig.Create(DefaultConfig.Instance)
// .WithOptions(ConfigOptions.DisableOptimizationsValidator)
//);
BenchmarkRunner.Run<ModbusBenchmark>(
ManualConfig.Create(DefaultConfig.Instance)
.WithOptions(ConfigOptions.DisableOptimizationsValidator)
);
// BenchmarkRunner.Run<S7Benchmark>(
//ManualConfig.Create(DefaultConfig.Instance)
//.WithOptions(ConfigOptions.DisableOptimizationsValidator)

View File

@@ -8,8 +8,8 @@
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\ThingsGateway.Foundation.Variable\ThingsGateway.Foundation.Variable.csproj"/>
<ProjectReference Include="..\ThingsGateway.Foundation.SourceGenerator\ThingsGateway.Foundation.SourceGenerator.csproj" PrivateAssets="all" OutputItemType="Analyzer" />
<ProjectReference Include="..\..\Foundation\ThingsGateway.Foundation.Variable\ThingsGateway.Foundation.Variable.csproj"/>
<ProjectReference Include="..\..\Foundation\ThingsGateway.Foundation.SourceGenerator\ThingsGateway.Foundation.SourceGenerator.csproj" PrivateAssets="all" OutputItemType="Analyzer" />
</ItemGroup>

View File

@@ -837,10 +837,16 @@ public class OpcUaMaster : IAsyncDisposable
{
foreach (var value in monitoreditem.DequeueValues())
{
var variableNode = ReadNode(monitoreditem.StartNodeId.ToString(), false, StatusCode.IsGood(value.StatusCode)).GetAwaiter().GetResult();
var variableNode = ReadNode(monitoreditem.StartNodeId.ToString(), false, StatusCode.IsGood(value.StatusCode)).GetAwaiter().GetResult();
if (value.Value != null)
{
if (value.Value.GetType().IsRichPrimitive())
{
DataChangedHandler?.Invoke((variableNode, monitoreditem, value, null));
continue;
}
var data = JsonUtils.Encode(m_session.MessageContext, TypeInfo.GetBuiltInType(variableNode.DataType, m_session.SystemContext.TypeTable), value.Value);
if (data == null && value.Value != null)
{

View File

@@ -12,6 +12,38 @@ namespace ThingsGateway.Foundation.OpcUa;
internal static class CollectionExtension
{
/// <summary>
/// 判断是否是元组类型
/// </summary>
/// <param name="type">类型</param>
/// <returns></returns>
internal static bool IsValueTuple(this Type type)
{
return type.Namespace == "System" && type.Name.Contains("ValueTuple`");
}
/// <summary>
/// 判断是否是富基元类型
/// </summary>
/// <param name="type">类型</param>
/// <returns></returns>
public static bool IsRichPrimitive(this Type? type)
{
if (type == null) return false;
// 处理元组类型
if (type.IsValueTuple()) return false;
// 处理数组类型,基元数组类型也可以是基元类型
if (type.IsArray) return type.GetElementType()?.IsRichPrimitive() ?? false;
// 基元类型或值类型或字符串类型
if (type.IsPrimitive || type.IsValueType || type == typeof(string)) return true;
if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>)) return type.GenericTypeArguments[0].IsRichPrimitive();
return false;
}
public static IEnumerable<List<T>> ChunkBetter<T>(this IEnumerable<T> source, int chunkSize)
{
if (source == null) throw new ArgumentNullException(nameof(source));

View File

@@ -12,9 +12,9 @@
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.0.0" />
<PackageReference Include="xunit" Version="2.9.3" />
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.4">
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.5">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>

View File

@@ -14,7 +14,6 @@ using ThingsGateway.Common;
using ThingsGateway.DB;
using ThingsGateway.Debug;
using ThingsGateway.Foundation;
using ThingsGateway.Gateway.Application;
using ThingsGateway.NewLife;
using ThingsGateway.NewLife.Extension;
using ThingsGateway.NewLife.Threading;

View File

@@ -45,7 +45,7 @@ public class SQLHistoryValue : IPrimaryIdEntity
///<summary>
///实时值
///</summary>
[SugarColumn(ColumnDescription = "实时值")]
[SugarColumn(ColumnDescription = "实时值", ColumnDataType = StaticConfig.CodeFirst_BigString, IsNullable = true)]
[AutoGenerateColumn(Order = 23, Visible = true, Sortable = true, Filterable = false)]
public string Value { get; set; }

View File

@@ -44,7 +44,7 @@ public class SQLRealValue : IPrimaryIdEntity
///<summary>
///实时值
///</summary>
[SugarColumn(ColumnDescription = "实时值")]
[SugarColumn(ColumnDescription = "实时值", ColumnDataType = StaticConfig.CodeFirst_BigString, IsNullable = true)]
[AutoGenerateColumn(Order = 21, Visible = true, Sortable = true, Filterable = false)]
public string Value { get; set; }

View File

@@ -93,6 +93,8 @@ public partial class SqlHistoryAlarm : BusinessBaseWithCacheAlarm
{
try
{
if (_driverPropertys.VariableAlarmEnable == false) return;
using var db = BusinessDatabaseUtil.GetDb((DbType)_driverPropertys.DbType, _driverPropertys.BigTextConnectStr);
if (!_driverPropertys.BigTextScriptHistoryTable.IsNullOrEmpty())
{

View File

@@ -11,8 +11,12 @@
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
#if NET10_0_OR_GREATER
using Microsoft.Extensions.Hosting;
#endif
using MQTTnet.AspNetCore;
using ThingsGateway.Foundation;

View File

@@ -12,7 +12,6 @@ using CSScripting;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using MQTTnet;
using MQTTnet.Internal;
@@ -43,7 +42,7 @@ public partial class MqttServer : BusinessBaseWithCacheIntervalScriptAll
#if NET10_0_OR_GREATER
private IHost _webHost { get; set; }
private Microsoft.Extensions.Hosting.IHost _webHost { get; set; }
#else
private IWebHost _webHost { get; set; }

View File

@@ -206,7 +206,9 @@ public class OpcDaMaster : CollectBase
if (TaskSchedulerLoop?.Stoped == true) return;
if (DisposedValue)
return;
LogMessage?.Trace($"{ToString()} Change:{Environment.NewLine} {values?.ToSystemTextJsonString()}");
if (LogMessage.LogLevel <= LogLevel.Trace)
LogMessage?.Trace($"{ToString()} Change:{Environment.NewLine} {values?.ToSystemTextJsonString()}");
foreach (var data in values)
{
@@ -215,11 +217,13 @@ public class OpcDaMaster : CollectBase
if (TaskSchedulerLoop?.Stoped == true) return;
if (DisposedValue)
return;
var type = data.Value.GetType();
if (data.Value is Array)
{
type = type.GetElementType();
}
//var type = data.Value.GetType();
//if (data.Value is Array)
//{
// type = type.GetElementType();
//}
if (!VariableAddresDicts.TryGetValue(data.Name, out var itemReads)) return;
foreach (var item in itemReads)

View File

@@ -164,8 +164,10 @@ public class OpcUaMaster : CollectBase
//如果是订阅模式,连接时添加订阅组
if (_plc.OpcUaProperty?.ActiveSubscribe == true && CurrentDevice.VariableSourceReads.Count > 0 && _plc.Session.SubscriptionCount < CurrentDevice.VariableSourceReads.Count)
{
if (cancellationToken.IsCancellationRequested) return;
foreach (var variableSourceRead in CurrentDevice.VariableSourceReads)
{
if (cancellationToken.IsCancellationRequested) return;
try
{
if (_plc.Session.Subscriptions.FirstOrDefault(a => a.DisplayName == variableSourceRead.RegisterAddress) == null)
@@ -186,8 +188,8 @@ public class OpcUaMaster : CollectBase
checkLog = false;
}
LogMessage?.LogInformation("AddSubscriptions done");
}
LogMessage?.LogInformation("AddSubscriptions done");
}
}
}
@@ -329,6 +331,7 @@ public class OpcUaMaster : CollectBase
private void DataChangedHandler((Opc.Ua.VariableNode variableNode, MonitoredItem monitoredItem, DataValue dataValue, JToken jToken) data)
{
DateTime time = DateTime.Now;
try
{
@@ -338,14 +341,15 @@ public class OpcUaMaster : CollectBase
return;
if (TaskSchedulerLoop?.Stoped == true) return;
LogMessage?.Trace($"Change: {Environment.NewLine} {data.monitoredItem.StartNodeId} : {data.jToken?.ToString()}");
if (LogMessage.LogLevel <= LogLevel.Trace)
LogMessage?.Trace($"Change: {Environment.NewLine} {data.monitoredItem.StartNodeId} : {data.jToken?.ToString() ?? data.dataValue?.Value?.ToJsonString()}");
//尝试固定点位的数据类型
var type = TypeInfo.GetSystemType(TypeInfo.GetBuiltInType(data.variableNode.DataType, _plc.Session.SystemContext.TypeTable), data.variableNode.ValueRank);
//var type = TypeInfo.GetSystemType(TypeInfo.GetBuiltInType(data.variableNode.DataType, _plc.Session.SystemContext.TypeTable), data.variableNode.ValueRank);
if (!VariableAddresDicts.TryGetValue(data.monitoredItem.StartNodeId.ToString(), out var itemReads)) return;
object value = data.jToken.GetObjectFromJToken();
object value = data.jToken?.GetObjectFromJToken() ?? data.dataValue?.Value;
var isGood = StatusCode.IsGood(data.dataValue.StatusCode);
if (_driverProperties.SourceTimestampEnable)
@@ -382,6 +386,8 @@ public class OpcUaMaster : CollectBase
LogMessage?.LogWarning(ex);
success = false;
}
}
#endif

View File

@@ -100,7 +100,7 @@ public partial class OpcUaMaster : IAsyncDisposable
LogMessage?.AddLogger(logger);
_plc.LogEvent = (a, b, c, d) => LogMessage?.Log((LogLevel)a, b, c, d);
_plc.DataChangedHandler += (a) => LogMessage?.Trace($"id:{a.monitoredItem?.StartNodeId};stateCode:{a.dataValue?.StatusCode};value:{a.jToken?.ToString()}");
_plc.DataChangedHandler += (a) => LogMessage?.Trace($"id:{a.monitoredItem?.StartNodeId};stateCode:{a.dataValue?.StatusCode};value:{a.jToken?.ToString() ?? a.dataValue?.Value?.ToJsonString()}");
base.OnInitialized();
}

View File

@@ -21,12 +21,12 @@
<base href="/" />
<title>ThingsGateway</title>
<link rel="stylesheet" href=@($"_content/BootstrapBlazor.FontAwesome/css/font-awesome.min.css?v={this.GetType().Assembly.GetName().Version}") />
<link rel="stylesheet" href=@($"_content/BootstrapBlazor/css/bootstrap.blazor.bundle.min.css?v={this.GetType().Assembly.GetName().Version}") />
<link rel="stylesheet" href=@($"_content/BootstrapBlazor/css/motronic.min.css?v={this.GetType().Assembly.GetName().Version}") />
<link rel="stylesheet" href=@($"ThingsGateway.Server.styles.css?v={this.GetType().Assembly.GetName().Version}") />
<link rel="stylesheet" href=@($"{WebsiteConst.DefaultResourceUrl}css/site.css?v={this.GetType().Assembly.GetName().Version}") />
<link rel="stylesheet" href=@($"{WebsiteConst.DefaultResourceUrl}css/devui.css?v={this.GetType().Assembly.GetName().Version}") />
<link rel="stylesheet" href=@($"_content/BootstrapBlazor.FontAwesome/css/font-awesome.min.css") />
<link rel="stylesheet" href=@($"_content/BootstrapBlazor/css/bootstrap.blazor.bundle.min.css") />
<link rel="stylesheet" href=@($"_content/BootstrapBlazor/css/motronic.min.css") />
<link rel="stylesheet" href=@($"ThingsGateway.Server.styles.css") />
<link rel="stylesheet" href=@($"{WebsiteConst.DefaultResourceUrl}css/site.css") />
<link rel="stylesheet" href=@($"{WebsiteConst.DefaultResourceUrl}css/devui.css") />
@* <script src=@($"{WebsiteConst.DefaultResourceUrl}js/theme.js") type="module"></script><!-- 初始主题 --> *@
<!-- PWA Manifest -->

View File

@@ -69,7 +69,7 @@
<Button @onclick="ShowAbout" class="layout-header-bar d-none d-lg-flex px-2" Icon="fa fa-info" Color="Color.None" TooltipText="@Localizer[nameof(About)]" />
}
@* 版本号 *@
<div class="px-1 navbar-header-text d-none d-lg-block">@_versionString</div>
<div class="px-1 navbar-header-text text-nowrap text-nowrap d-none d-lg-block">@_versionString</div>
@* 主题切换 *@
@* <ThemeToggle /> *@

View File

@@ -1,6 +1,6 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.9.34622.214
# Visual Studio Version 18
VisualStudioVersion = 18.0.11018.127 d18.0
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ThingsGateway.Server", "ThingsGateway.Server\ThingsGateway.Server.csproj", "{22875EFB-DADF-4612-A572-33BCC092F644}"
EndProject
@@ -111,12 +111,16 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "foundation", "foundation",
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "debug", "debug", "{053AB5FA-9742-96EC-76A1-2AEC739860C6}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ThingsGateway.Foundation.Demo", "Foundation\ThingsGateway.Foundation.Demo\ThingsGateway.Foundation.Demo.csproj", "{520DEEAA-1CBD-C0CB-2363-EB190D7DE4EA}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ThingsGateway.Foundation.Benchmark", "Plugin\ThingsGateway.Foundation.Benchmark\ThingsGateway.Foundation.Benchmark.csproj", "{B0957BD6-CF77-36E7-B657-2D0DB85F386F}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ThingsGateway.ScriptDebug", "ThingsGateway.ScriptDebug\ThingsGateway.ScriptDebug.csproj", "{F4AC662F-BE2C-6E1C-4BAF-370B968B3554}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "demo", "demo", "{4F7AAAB1-C607-4FA0-9DFC-562940D76BC1}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "benchmark", "benchmark", "{19611184-C452-4DF7-A0A3-CCFFCE1DD191}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ThingsGateway.Foundation.Demo", "Plugin\ThingsGateway.Foundation.Demo\ThingsGateway.Foundation.Demo.csproj", "{C9B6F478-8CEC-1C71-00C2-19442F731E0E}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -287,10 +291,6 @@ Global
{EAEE6A03-D2E7-7283-0F7A-F15B6261EE96}.Debug|Any CPU.Build.0 = Debug|Any CPU
{EAEE6A03-D2E7-7283-0F7A-F15B6261EE96}.Release|Any CPU.ActiveCfg = Release|Any CPU
{EAEE6A03-D2E7-7283-0F7A-F15B6261EE96}.Release|Any CPU.Build.0 = Release|Any CPU
{520DEEAA-1CBD-C0CB-2363-EB190D7DE4EA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{520DEEAA-1CBD-C0CB-2363-EB190D7DE4EA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{520DEEAA-1CBD-C0CB-2363-EB190D7DE4EA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{520DEEAA-1CBD-C0CB-2363-EB190D7DE4EA}.Release|Any CPU.Build.0 = Release|Any CPU
{B0957BD6-CF77-36E7-B657-2D0DB85F386F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B0957BD6-CF77-36E7-B657-2D0DB85F386F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B0957BD6-CF77-36E7-B657-2D0DB85F386F}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -299,6 +299,10 @@ Global
{F4AC662F-BE2C-6E1C-4BAF-370B968B3554}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F4AC662F-BE2C-6E1C-4BAF-370B968B3554}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F4AC662F-BE2C-6E1C-4BAF-370B968B3554}.Release|Any CPU.Build.0 = Release|Any CPU
{C9B6F478-8CEC-1C71-00C2-19442F731E0E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C9B6F478-8CEC-1C71-00C2-19442F731E0E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C9B6F478-8CEC-1C71-00C2-19442F731E0E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C9B6F478-8CEC-1C71-00C2-19442F731E0E}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -345,12 +349,14 @@ Global
{1D9CD7A3-9700-A851-0ABD-183347D9CC33} = {36510D70-161F-4241-B8D0-781E21032816}
{E6EF2033-F02A-CDAD-5A72-EE397A89742E} = {36510D70-161F-4241-B8D0-781E21032816}
{053AB5FA-9742-96EC-76A1-2AEC739860C6} = {36510D70-161F-4241-B8D0-781E21032816}
{520DEEAA-1CBD-C0CB-2363-EB190D7DE4EA} = {2AC600BB-4325-4E0A-93A7-B1F53C8E2CA7}
{B0957BD6-CF77-36E7-B657-2D0DB85F386F} = {1D9CD7A3-9700-A851-0ABD-183347D9CC33}
{B0957BD6-CF77-36E7-B657-2D0DB85F386F} = {19611184-C452-4DF7-A0A3-CCFFCE1DD191}
{4F7AAAB1-C607-4FA0-9DFC-562940D76BC1} = {36510D70-161F-4241-B8D0-781E21032816}
{19611184-C452-4DF7-A0A3-CCFFCE1DD191} = {36510D70-161F-4241-B8D0-781E21032816}
{C9B6F478-8CEC-1C71-00C2-19442F731E0E} = {4F7AAAB1-C607-4FA0-9DFC-562940D76BC1}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
RESX_Rules = {"EnabledRules":[]}
RESX_NeutralResourcesLanguage = zh-Hans
SolutionGuid = {199B1B96-4F56-4828-9531-813BA02DB282}
RESX_NeutralResourcesLanguage = zh-Hans
RESX_Rules = {"EnabledRules":[]}
EndGlobalSection
EndGlobal