Compare commits

...

4 Commits

Author SHA1 Message Date
Kimdiego2098
24bc60abf0 fix:signalR razor dispose接口 2023-10-09 18:11:23 +08:00
Kimdiego2098
31eee6b009 update uaparser 2023-10-09 10:56:22 +08:00
Kimdiego2098
c5da565a8f 添加MqttRpcDemo 2023-10-07 12:04:17 +08:00
Kimdiego2098
947cd712e1 添加清理日志任务配置参数 2023-10-06 18:28:06 +08:00
31 changed files with 784 additions and 52 deletions

View File

@@ -1,6 +1,6 @@
<Project>
<PropertyGroup>
<Version>3.0.0.2</Version>
<Version>3.0.0.4</Version>
<LangVersion>latest</LangVersion>
<TargetFrameworks>net6.0;net7.0</TargetFrameworks>
<Authors>Diego</Authors>

View File

@@ -124,6 +124,15 @@ public partial class MainLayout
"Title": "OPCUAClient"
}
]
},
{
"Title": "Mqtt",
"Children": [
{
"Href": "/MqttClient",
"Title": "MqttClient"
}
]
}
]

View File

@@ -45,6 +45,13 @@
<ItemGroup>
<Compile Include="..\..\Plugin\ThingsGateway.Plugin.DLT645\Page\DLT645_2007DebugPage.razor.cs" Link="Pages\DLT645\DLT645_2007DebugPage.razor.cs" />
<Compile Include="..\..\Plugin\ThingsGateway.Plugin.Mqtt\MqttRpcNameVaueWithId.cs" Link="Pages\Mqtt\MqttRpcNameVaueWithId.cs" />
<Compile Include="..\..\Plugin\ThingsGateway.Plugin.Mqtt\Page\MqttClientDebugPage.razor.cs" Link="Pages\Mqtt\MqttClientDebugPage.razor.cs" />
<Compile Include="..\..\Plugin\ThingsGateway.Plugin.Mqtt\Page\MqttClientPage.razor.cs" Link="Pages\Mqtt\MqttClientPage.razor.cs" />
<Compile Include="..\..\Plugin\ThingsGateway.Plugin.Mqtt\PrivateLogger.cs" Link="Pages\Mqtt\PrivateLogger.cs" />
<Compile Include="..\..\Plugin\ThingsGateway.Plugin.Mqtt\RpcClass\MqttRpcClient.cs" Link="Pages\Mqtt\MqttRpcClient.cs" />
<Compile Include="..\..\Plugin\ThingsGateway.Plugin.Mqtt\RpcClass\MqttRpcClientExtensions.cs" Link="Pages\Mqtt\MqttRpcClientExtensions.cs" />
<Compile Include="..\..\Plugin\ThingsGateway.Plugin.Mqtt\RpcClass\MqttRpcTopicPair.cs" Link="Pages\Mqtt\MqttRpcTopicPair.cs" />
<Content Include="..\..\Plugin\ThingsGateway.Plugin.DLT645\Page\DLT645_2007DebugPage.razor" Link="Pages\DLT645\DLT645_2007DebugPage.razor" />
<Compile Include="..\..\Plugin\ThingsGateway.Plugin.DLT645\Page\DLT645_2007OverTcpDebugPage.razor.cs" Link="Pages\DLT645\DLT645_2007OverTcpDebugPage.razor.cs" />
<Content Include="..\..\Plugin\ThingsGateway.Plugin.DLT645\Page\DLT645_2007OverTcpDebugPage.razor" Link="Pages\DLT645\DLT645_2007OverTcpDebugPage.razor" />
@@ -117,5 +124,17 @@
</ItemGroup>
<ItemGroup>
<Folder Include="Pages\Mqtt\" />
</ItemGroup>
<ItemGroup>
<Content Include="..\..\Plugin\ThingsGateway.Plugin.Mqtt\Page\MqttClientDebugPage.razor" Link="Pages\Mqtt\MqttClientDebugPage.razor" />
<Content Include="..\..\Plugin\ThingsGateway.Plugin.Mqtt\Page\MqttClientPage.razor" Link="Pages\Mqtt\MqttClientPage.razor" />
<PackageReference Include="MQTTnet" Version="4.3.1.873" />
</ItemGroup>
</Project>

View File

@@ -1,6 +1,6 @@
<Project>
<PropertyGroup>
<Version>3.0.0.2</Version>
<Version>3.0.0.4</Version>
<GenerateDocumentationFile>True</GenerateDocumentationFile>
<LangVersion>latest</LangVersion>
<TargetFrameworks>net45;netstandard2.0;net6.0;net7.0</TargetFrameworks>

View File

@@ -440,9 +440,7 @@ public class TcpClientBase : BaseSocket, ITcpClient
{
throw new TimeoutException("连接超时");
}
{
success(socket);
}
success(socket);
#else
using CancellationTokenSource cancellationTokenSource = new();
@@ -465,11 +463,10 @@ public class TcpClientBase : BaseSocket, ITcpClient
else
{
socket?.SafeDispose();
socket.Close();
throw new TimeoutException("连接超时");
}
#endif
}
finally
{

View File

@@ -1,6 +1,6 @@
<Project>
<PropertyGroup>
<Version>3.0.0.2</Version>
<Version>3.0.0.4</Version>
<LangVersion>latest</LangVersion>
<ImplicitUsings>enable</ImplicitUsings>
<TargetFrameworks>net6.0;net7.0</TargetFrameworks>
@@ -11,7 +11,6 @@
<SignAssembly>True</SignAssembly>
<DelaySign>False</DelaySign>
<SatelliteResourceLanguages>zh-Hans</SatelliteResourceLanguages>
<GenerateDocumentationFile>True</GenerateDocumentationFile>
</PropertyGroup>

View File

@@ -10,6 +10,9 @@
//------------------------------------------------------------------------------
#endregion
global using ThingsGateway.Components;
global using ThingsGateway.Foundation;
global using ThingsGateway.Foundation.Serial;
global using ThingsGateway.Foundation.Sockets;
global using ThingsGateway.Gateway.Application;
global using ThingsGateway.Gateway.Core;

View File

@@ -22,6 +22,7 @@ using MQTTnet.Client;
using System.Collections.Concurrent;
using System.Text;
using ThingsGateway.Foundation.Demo;
using ThingsGateway.Foundation.Extension.ConcurrentQueue;
using ThingsGateway.Foundation.Extension.Generic;
using ThingsGateway.Foundation.Extension.String;
@@ -55,7 +56,7 @@ public class MqttClient : UpLoadBase
private TimerTick exVariableTimerTick;
/// <inheritdoc/>
public override Type DriverDebugUIType => null;
public override Type DriverDebugUIType => typeof(MqttClientDebugPage);
/// <inheritdoc/>
public override UpDriverPropertyBase DriverPropertys => driverPropertys;

View File

@@ -10,6 +10,10 @@
//------------------------------------------------------------------------------
#endregion
using System.Collections.Generic;
using ThingsGateway.Foundation;
namespace ThingsGateway.Plugin.Mqtt;
/// <summary>

View File

@@ -0,0 +1,85 @@
@*
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
*@
@page "/MqttClient"
@namespace ThingsGateway.Foundation.Demo
@using BlazorComponent;
@using Microsoft.AspNetCore.Components.Web;
@using Masa.Blazor.Presets;
@using Microsoft.JSInterop;
@using ThingsGateway.Foundation;
@using ThingsGateway.Foundation.Extension;
@using Masa.Blazor
<MqttClientPage @ref=mqttClientPage></MqttClientPage>
<DriverDebugUIPage @ref=driverDebugUIPage Sections="_sections" ShowDefaultOtherContent=false>
<ReadWriteContent>
<div class="my-1 py-1">
<MTextarea Class="mx-1 my-1" Label="订阅主题" Dense Outlined HideDetails="@("auto")" @bind-Value=@driverDebugUIPage.Address />
<MButton Class="mx-1 my-1" Color="primary" OnClick="SubscribeAsync">
添加
</MButton>
<MButton Class="mx-1 my-1" Color="primary" OnClick="UnsubscribeAsync">
移除
</MButton>
<MTextarea Class="mx-1 my-1" Label="发布主题" Dense Outlined HideDetails="@("auto")" @bind-Value=@PublishTopic />
<MTextarea Class="mx-1 my-1" Label="发布内容" Dense Outlined HideDetails="@("auto")" @bind-Value=@PublishValue />
<MButton Class="mx-1 my-1" Color="primary" OnClick="PublishAsync">
发布
</MButton>
<MSubheader Class="mt-4 font-weight-black"> Rpc </MSubheader>
<MTextarea Class="mx-1 mt-3 my-1" Label="Rpc主题" Dense Outlined HideDetails="@("auto")" @bind-Value=@MqttRpcTopicPair.RequestTopic />
<MTextarea Class="mx-1 mt-3 my-1" Label="Rpc返回主题" Dense Outlined HideDetails="@("auto")" @bind-Value=@MqttRpcTopicPair.ResponseTopic />
<MTextarea Class="mx-1 mt-3 my-1" Label="Rpc内容" Dense Outlined HideDetails="@("auto")" @bind-Value=@driverDebugUIPage.WriteValue />
<MButton Class="mx-1 my-1" Color="primary" OnClick="RpcExecuteAsync">
执行
</MButton>
</div>
<MCol Class="my-1 py-1">
<MRow NoGutters>
</MRow>
</MCol>
</ReadWriteContent>
</DriverDebugUIPage>
@code {
private readonly List<(string Code, string Language)> _sections = new();
}

View File

@@ -0,0 +1,154 @@
#region copyright
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
using Masa.Blazor;
using Microsoft.AspNetCore.Components;
using MQTTnet;
using MQTTnet.Extensions.Rpc;
using System.Collections.Generic;
using System.Text;
using ThingsGateway.Plugin.Mqtt;
namespace ThingsGateway.Foundation.Demo;
/// <summary>
/// MqttClientDebugPage
/// </summary>
public partial class MqttClientDebugPage : IDisposable
{
private DriverDebugUIPage driverDebugUIPage;
private MqttClientPage mqttClientPage;
[Inject]
private InitTimezone InitTimezone { get; set; }
[Inject]
IPopupService PopupService { get; set; }
/// <inheritdoc/>
public void Dispose()
{
mqttClientPage.SafeDispose();
}
/// <inheritdoc/>
protected override void OnAfterRender(bool firstRender)
{
if (firstRender)
{
if (mqttClientPage != null)
{
mqttClientPage.LogAction = driverDebugUIPage.LogOut;
}
//初始化
driverDebugUIPage.Address = "ThingsGateway/Variable";
driverDebugUIPage.WriteValue = new MqttRpcNameVaueWithId()
{
RpcId = Guid.NewGuid().ToString(),
WriteInfos = new Dictionary<string, string>()
{
{ "tag1", "123" }
}
}.ToJsonString();
;
mqttClientPage.IP = "127.0.0.1";
mqttClientPage.Port = 1883;
mqttClientPage.UserName = "admin";
mqttClientPage.Password = "111111";
mqttClientPage.StateHasChangedAsync();
//载入配置
StateHasChanged();
driverDebugUIPage.Sections.Clear();
}
base.OnAfterRender(firstRender);
}
private async Task SubscribeAsync()
{
try
{
var mqttSubscribeOptions = mqttClientPage.MqttFactory.CreateSubscribeOptionsBuilder()
.WithTopicFilter(
f =>
{
f.WithTopic(driverDebugUIPage.Address);
})
.Build();
await mqttClientPage.MqttClient.SubscribeAsync(mqttSubscribeOptions, CancellationToken.None);
driverDebugUIPage.Messages.Add((Microsoft.Extensions.Logging.LogLevel.Information, DateTimeExtensions.CurrentDateTime.ToDefaultDateTimeFormat(driverDebugUIPage.InitTimezone.TimezoneOffset) + " - " + $"订阅{driverDebugUIPage.Address}成功"));
}
catch (Exception ex)
{
driverDebugUIPage.Messages.Add((Microsoft.Extensions.Logging.LogLevel.Error, DateTimeExtensions.CurrentDateTime.ToDefaultDateTimeFormat(driverDebugUIPage.InitTimezone.TimezoneOffset) + " - " + ex.Message));
}
}
private async Task UnsubscribeAsync()
{
try
{
var mqttSubscribeOptions = mqttClientPage.MqttFactory.CreateUnsubscribeOptionsBuilder()
.WithTopicFilter(driverDebugUIPage.Address)
.Build();
await mqttClientPage.MqttClient.UnsubscribeAsync(mqttSubscribeOptions, CancellationToken.None);
driverDebugUIPage.Messages.Add((Microsoft.Extensions.Logging.LogLevel.Information, DateTimeExtensions.CurrentDateTime.ToDefaultDateTimeFormat(driverDebugUIPage.InitTimezone.TimezoneOffset) + " - " + $"取消订阅{driverDebugUIPage.Address}成功"));
}
catch (Exception ex)
{
driverDebugUIPage.Messages.Add((Microsoft.Extensions.Logging.LogLevel.Error, DateTimeExtensions.CurrentDateTime.ToDefaultDateTimeFormat(driverDebugUIPage.InitTimezone.TimezoneOffset) + " - " + ex.Message));
}
}
string PublishTopic;
string PublishValue;
private async Task PublishAsync()
{
try
{
var devMessage = new MqttApplicationMessageBuilder()
.WithTopic($"{PublishTopic}")
.WithPayload(PublishValue).Build();
await mqttClientPage.MqttClient.PublishAsync(devMessage, CancellationToken.None);
driverDebugUIPage.Messages.Add((Microsoft.Extensions.Logging.LogLevel.Information, DateTimeExtensions.CurrentDateTime.ToDefaultDateTimeFormat(driverDebugUIPage.InitTimezone.TimezoneOffset) + " - " + $"发布{PublishTopic}成功"));
}
catch (Exception ex)
{
driverDebugUIPage.Messages.Add((Microsoft.Extensions.Logging.LogLevel.Error, DateTimeExtensions.CurrentDateTime.ToDefaultDateTimeFormat(driverDebugUIPage.InitTimezone.TimezoneOffset) + " - " + ex.Message));
}
}
MqttRpcTopicPair MqttRpcTopicPair = new() { RequestTopic = "ThingsGateway/RpcWrite", ResponseTopic = "ThingsGateway/RpcSub" };
private async Task RpcExecuteAsync()
{
try
{
using MqttRpcClient mqttRpcClient = new(mqttClientPage.MqttClient);
var data = await mqttRpcClient.ExecuteAsync(MqttRpcTopicPair, driverDebugUIPage.WriteValue, MQTTnet.Protocol.MqttQualityOfServiceLevel.AtMostOnce, TimeSpan.FromSeconds(10));
var str = Encoding.UTF8.GetString(data);
driverDebugUIPage.Messages.Add((Microsoft.Extensions.Logging.LogLevel.Information, DateTimeExtensions.CurrentDateTime.ToDefaultDateTimeFormat(driverDebugUIPage.InitTimezone.TimezoneOffset) + " - " + str));
}
catch (Exception ex)
{
driverDebugUIPage.Messages.Add((Microsoft.Extensions.Logging.LogLevel.Error, DateTimeExtensions.CurrentDateTime.ToDefaultDateTimeFormat(driverDebugUIPage.InitTimezone.TimezoneOffset) + " - " + ex.Message));
}
}
}

View File

@@ -0,0 +1,42 @@
@*
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
*@
@namespace ThingsGateway.Foundation.Demo
@using BlazorComponent;
@using Microsoft.AspNetCore.Components.Web;
@using System.IO.Ports;
@using System.Collections.Concurrent;
@using ThingsGateway.Foundation;
@using Masa.Blazor
@implements IDisposable
<MCard Elevation="1" Rounded="false" Class="pa-2" Style="width:100%">
<div class="mb-4">设备配置</div>
<MRow Justify="JustifyTypes.Start" Align="AlignTypes.Center">
<MTextField Class="ma-1" Label=IP Dense Outlined HideDetails="@("auto")" @bind-Value=@IP />
<MTextField Class="ma-1" Label=端口 Dense Outlined HideDetails="@("auto")" @bind-Value=@Port />
<MTextField Class="ma-1" Label=ID Dense Outlined HideDetails="@("auto")" @bind-Value=@ConnectId />
<MTextField Class="ma-1" Label=用户 Dense Outlined HideDetails="@("auto")" @bind-Value=@UserName />
<MTextField Class="ma-1" Label=密码 Dense Outlined HideDetails="@("auto")" @bind-Value=@Password />
<MButton Class="ma-1" OnClick=@Connect Color="primary">
连接
</MButton>
<MButton Class="ma-1" OnClick=@DisConnect Color="red">
断开
</MButton>
</MRow>
</MCard>

View File

@@ -0,0 +1,119 @@
#region copyright
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
using MQTTnet;
using MQTTnet.Client;
using System.Text;
using ThingsGateway.Plugin.Mqtt;
namespace ThingsGateway.Foundation.Demo;
/// <summary>
/// MqttClientPage
/// </summary>
public partial class MqttClientPage
{
/// <summary>
/// 日志输出
/// </summary>
public Action<LogLevel, object, string, Exception> LogAction;
/// <summary>
/// OPC
/// </summary>
public IMqttClient MqttClient;
public MqttClientOptions MqttClientOptions;
public MqttFactory MqttFactory;
public string ConnectId;
public string IP;
public string Password;
public int Port;
public string UserName;
/// <summary>
/// <inheritdoc/>
/// </summary>
public void Dispose()
{
MqttClient.SafeDispose();
}
/// <inheritdoc/>
protected override void OnInitialized()
{
MqttFactory = new MqttFactory(new PrivateLogger(new EasyLogger(LogAction) { LogLevel = LogLevel.Trace }));
MqttClient = MqttFactory.CreateMqttClient();
MqttClient.ApplicationMessageReceivedAsync += MqttClient_ApplicationMessageReceivedAsync;
base.OnInitialized();
}
private async Task Connect()
{
try
{
await MqttClient.DisconnectAsync();
await GetMqttClient();
}
catch (Exception ex)
{
LogAction?.Invoke(LogLevel.Error, null, null, ex);
}
}
private async Task DisConnect()
{
try
{
await MqttClient.DisconnectAsync();
}
catch (Exception ex)
{
LogAction?.Invoke(LogLevel.Error, null, null, ex);
}
}
private async Task<IMqttClient> GetMqttClient()
{
//载入配置
MqttClientOptions = MqttFactory.CreateClientOptionsBuilder()
.WithClientId(ConnectId)
.WithCredentials(UserName, Password)//账密
.WithTcpServer(IP, Port)//服务器
.WithCleanSession(true)
.WithKeepAlivePeriod(TimeSpan.FromSeconds(120.0))
.WithoutThrowOnNonSuccessfulConnectResponse()
.Build();
await MqttClient.ConnectAsync(MqttClientOptions);
return MqttClient;
}
private void LogOut(byte logLevel, object source, string message, Exception exception) => LogAction?.Invoke((LogLevel)logLevel, source, message, exception);
private Task MqttClient_ApplicationMessageReceivedAsync(MqttApplicationMessageReceivedEventArgs args)
{
LogAction?.Invoke(LogLevel.Info, this, $"[{args.ApplicationMessage.Topic}]{Encoding.UTF8.GetString(args.ApplicationMessage.PayloadSegment)}", null);
return Task.CompletedTask;
}
public Task StateHasChangedAsync()
{
return InvokeAsync(StateHasChanged);
}
}

View File

@@ -12,6 +12,8 @@
using MQTTnet.Diagnostics;
using ThingsGateway.Foundation;
namespace ThingsGateway.Plugin.Mqtt
{
internal class PrivateLogger : IMqttNetLogger

View File

@@ -0,0 +1,147 @@
#region copyright
//------------------------------------------------------------------------------
// <20>˴<EFBFBD><CBB4><EFBFBD><EFBFBD><EFBFBD>Ȩ<EFBFBD><C8A8><EFBFBD><EFBFBD>Ϊȫ<CEAA>ļ<EFBFBD><C4BC><EFBFBD><EFBFBD>ǣ<EFBFBD><C7A3><EFBFBD><EFBFBD><EFBFBD>ԭ<EFBFBD><D4AD><EFBFBD><EFBFBD><EFBFBD>ر<EFBFBD><D8B1><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>·<EFBFBD><C2B7>ֶ<EFBFBD><D6B6><EFBFBD><EFBFBD><EFBFBD>
// <20>˴<EFBFBD><CBB4><EFBFBD><EFBFBD><EFBFBD>Ȩ<EFBFBD><C8A8><EFBFBD><EFBFBD><EFBFBD>ر<EFBFBD><D8B1><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ĵ<EFBFBD><C4B4><EFBFBD><EBA3A9><EFBFBD><EFBFBD><EFBFBD>߱<EFBFBD><DFB1><EFBFBD>Diego<67><6F><EFBFBD><EFBFBD>
// Դ<><D4B4><EFBFBD><EFBFBD>ʹ<EFBFBD><CAB9>Э<EFBFBD><D0AD><EFBFBD><EFBFBD>ѭ<EFBFBD><D1AD><EFBFBD>ֿ<EFBFBD><D6BF>Ŀ<EFBFBD>ԴЭ<D4B4><EFBFBD><E9BCB0><EFBFBD><EFBFBD>Э<EFBFBD><D0AD>
// GiteeԴ<65><D4B4><EFBFBD><EFBFBD><EFBFBD>ֿ⣺https://gitee.com/diego2098/ThingsGateway
// GithubԴ<62><D4B4><EFBFBD><EFBFBD><EFBFBD>ֿ⣺https://github.com/kimdiego2098/ThingsGateway
// ʹ<><CAB9><EFBFBD>ĵ<EFBFBD><C4B5><EFBFBD>https://diego2098.gitee.io/thingsgateway-docs/
// QQȺ<51><C8BA>605534569
//------------------------------------------------------------------------------
#endregion
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using MQTTnet.Client;
using MQTTnet.Exceptions;
using MQTTnet.Formatter;
using MQTTnet.Internal;
using MQTTnet.Protocol;
using System.Collections.Concurrent;
namespace MQTTnet.Extensions.Rpc
{
public sealed class MqttRpcClient : IDisposable
{
readonly IMqttClient _mqttClient;
readonly ConcurrentDictionary<string, AsyncTaskCompletionSource<byte[]>> _waitingCalls = new ConcurrentDictionary<string, AsyncTaskCompletionSource<byte[]>>();
public MqttRpcClient(IMqttClient mqttClient)
{
_mqttClient = mqttClient ?? throw new ArgumentNullException(nameof(mqttClient));
_mqttClient.ApplicationMessageReceivedAsync += HandleApplicationMessageReceivedAsync;
}
public void Dispose()
{
_mqttClient.ApplicationMessageReceivedAsync -= HandleApplicationMessageReceivedAsync;
foreach (var tcs in _waitingCalls)
{
tcs.Value.TrySetCanceled();
}
_waitingCalls.Clear();
}
public async Task<byte[]> ExecuteAsync(MqttRpcTopicPair mqttRpcTopicPair, byte[] payload, MqttQualityOfServiceLevel qualityOfServiceLevel, TimeSpan timeout)
{
using var timeoutToken = new CancellationTokenSource(timeout);
try
{
return await ExecuteAsync(mqttRpcTopicPair, payload, qualityOfServiceLevel, timeoutToken.Token).ConfigureAwait(false);
}
catch (OperationCanceledException exception)
{
if (timeoutToken.IsCancellationRequested)
{
throw new MqttCommunicationTimedOutException(exception);
}
throw;
}
}
public async Task<byte[]> ExecuteAsync(MqttRpcTopicPair mqttRpcTopicPair, byte[] payload, MqttQualityOfServiceLevel qualityOfServiceLevel, CancellationToken cancellationToken = default)
{
if (mqttRpcTopicPair == null)
{
throw new ArgumentNullException(nameof(mqttRpcTopicPair));
}
var requestTopic = mqttRpcTopicPair.RequestTopic;
var responseTopic = mqttRpcTopicPair.ResponseTopic;
if (string.IsNullOrWhiteSpace(requestTopic))
{
throw new MqttProtocolViolationException("RPC request topic is empty.");
}
if (string.IsNullOrWhiteSpace(responseTopic))
{
throw new MqttProtocolViolationException("RPC response topic is empty.");
}
var requestMessageBuilder = new MqttApplicationMessageBuilder().WithTopic(requestTopic).WithPayload(payload).WithQualityOfServiceLevel(qualityOfServiceLevel);
if (_mqttClient.Options.ProtocolVersion == MqttProtocolVersion.V500)
{
requestMessageBuilder.WithResponseTopic(responseTopic);
}
var requestMessage = requestMessageBuilder.Build();
try
{
var awaitable = new AsyncTaskCompletionSource<byte[]>();
if (!_waitingCalls.TryAdd(responseTopic, awaitable))
{
throw new InvalidOperationException();
}
var subscribeOptions = new MqttClientSubscribeOptionsBuilder().WithTopicFilter(responseTopic, qualityOfServiceLevel).Build();
await _mqttClient.SubscribeAsync(subscribeOptions, cancellationToken).ConfigureAwait(false);
await _mqttClient.PublishAsync(requestMessage, cancellationToken).ConfigureAwait(false);
using (cancellationToken.Register(
() =>
{
awaitable.TrySetCanceled();
}))
{
return await awaitable.Task.ConfigureAwait(false);
}
}
finally
{
_waitingCalls.TryRemove(responseTopic, out _);
await _mqttClient.UnsubscribeAsync(responseTopic, CancellationToken.None).ConfigureAwait(false);
}
}
Task HandleApplicationMessageReceivedAsync(MqttApplicationMessageReceivedEventArgs eventArgs)
{
if (!_waitingCalls.TryRemove(eventArgs.ApplicationMessage.Topic, out var awaitable))
{
return CompletedTask.Instance;
}
var payloadBuffer = eventArgs.ApplicationMessage.PayloadSegment.ToArray();
awaitable.TrySetResult(payloadBuffer);
// Set this message to handled to that other code can avoid execution etc.
eventArgs.IsHandled = true;
return CompletedTask.Instance;
}
}
}

View File

@@ -0,0 +1,34 @@
#region copyright
//------------------------------------------------------------------------------
// <20>˴<EFBFBD><CBB4><EFBFBD><EFBFBD><EFBFBD>Ȩ<EFBFBD><C8A8><EFBFBD><EFBFBD>Ϊȫ<CEAA>ļ<EFBFBD><C4BC><EFBFBD><EFBFBD>ǣ<EFBFBD><C7A3><EFBFBD><EFBFBD><EFBFBD>ԭ<EFBFBD><D4AD><EFBFBD><EFBFBD><EFBFBD>ر<EFBFBD><D8B1><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>·<EFBFBD><C2B7>ֶ<EFBFBD><D6B6><EFBFBD><EFBFBD><EFBFBD>
// <20>˴<EFBFBD><CBB4><EFBFBD><EFBFBD><EFBFBD>Ȩ<EFBFBD><C8A8><EFBFBD><EFBFBD><EFBFBD>ر<EFBFBD><D8B1><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ĵ<EFBFBD><C4B4><EFBFBD><EBA3A9><EFBFBD><EFBFBD><EFBFBD>߱<EFBFBD><DFB1><EFBFBD>Diego<67><6F><EFBFBD><EFBFBD>
// Դ<><D4B4><EFBFBD><EFBFBD>ʹ<EFBFBD><CAB9>Э<EFBFBD><D0AD><EFBFBD><EFBFBD>ѭ<EFBFBD><D1AD><EFBFBD>ֿ<EFBFBD><D6BF>Ŀ<EFBFBD>ԴЭ<D4B4><EFBFBD><E9BCB0><EFBFBD><EFBFBD>Э<EFBFBD><D0AD>
// GiteeԴ<65><D4B4><EFBFBD><EFBFBD><EFBFBD>ֿ⣺https://gitee.com/diego2098/ThingsGateway
// GithubԴ<62><D4B4><EFBFBD><EFBFBD><EFBFBD>ֿ⣺https://github.com/kimdiego2098/ThingsGateway
// ʹ<><CAB9><EFBFBD>ĵ<EFBFBD><C4B5><EFBFBD>https://diego2098.gitee.io/thingsgateway-docs/
// QQȺ<51><C8BA>605534569
//------------------------------------------------------------------------------
#endregion
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using MQTTnet.Protocol;
using System.Text;
namespace MQTTnet.Extensions.Rpc
{
public static class MqttRpcClientExtensions
{
public static Task<byte[]> ExecuteAsync(this MqttRpcClient client, MqttRpcTopicPair mqttRpcTopicPair, string payload, MqttQualityOfServiceLevel qualityOfServiceLevel, TimeSpan timeout)
{
if (client == null) throw new ArgumentNullException(nameof(client));
var buffer = Encoding.UTF8.GetBytes(payload ?? string.Empty);
return client.ExecuteAsync(mqttRpcTopicPair, buffer, qualityOfServiceLevel, timeout);
}
}
}

View File

@@ -0,0 +1,25 @@
#region copyright
//------------------------------------------------------------------------------
// <20>˴<EFBFBD><CBB4><EFBFBD><EFBFBD><EFBFBD>Ȩ<EFBFBD><C8A8><EFBFBD><EFBFBD>Ϊȫ<CEAA>ļ<EFBFBD><C4BC><EFBFBD><EFBFBD>ǣ<EFBFBD><C7A3><EFBFBD><EFBFBD><EFBFBD>ԭ<EFBFBD><D4AD><EFBFBD><EFBFBD><EFBFBD>ر<EFBFBD><D8B1><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>·<EFBFBD><C2B7>ֶ<EFBFBD><D6B6><EFBFBD><EFBFBD><EFBFBD>
// <20>˴<EFBFBD><CBB4><EFBFBD><EFBFBD><EFBFBD>Ȩ<EFBFBD><C8A8><EFBFBD><EFBFBD><EFBFBD>ر<EFBFBD><D8B1><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ĵ<EFBFBD><C4B4><EFBFBD><EBA3A9><EFBFBD><EFBFBD><EFBFBD>߱<EFBFBD><DFB1><EFBFBD>Diego<67><6F><EFBFBD><EFBFBD>
// Դ<><D4B4><EFBFBD><EFBFBD>ʹ<EFBFBD><CAB9>Э<EFBFBD><D0AD><EFBFBD><EFBFBD>ѭ<EFBFBD><D1AD><EFBFBD>ֿ<EFBFBD><D6BF>Ŀ<EFBFBD>ԴЭ<D4B4><EFBFBD><E9BCB0><EFBFBD><EFBFBD>Э<EFBFBD><D0AD>
// GiteeԴ<65><D4B4><EFBFBD><EFBFBD><EFBFBD>ֿ⣺https://gitee.com/diego2098/ThingsGateway
// GithubԴ<62><D4B4><EFBFBD><EFBFBD><EFBFBD>ֿ⣺https://github.com/kimdiego2098/ThingsGateway
// ʹ<><CAB9><EFBFBD>ĵ<EFBFBD><C4B5><EFBFBD>https://diego2098.gitee.io/thingsgateway-docs/
// QQȺ<51><C8BA>605534569
//------------------------------------------------------------------------------
#endregion
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
namespace MQTTnet.Extensions.Rpc
{
public sealed class MqttRpcTopicPair
{
public string RequestTopic { get; set; }
public string ResponseTopic { get; set; }
}
}

View File

@@ -1,6 +1,5 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk.Razor">
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
@@ -9,7 +8,30 @@
</Target>
<ItemGroup>
<ProjectReference Include="..\..\Web\ThingsGateway.Components\ThingsGateway.Components.csproj">
<Private>false</Private>
<CopyLocalSatelliteAssemblies>false</CopyLocalSatelliteAssemblies>
<ExcludeAssets>runtime</ExcludeAssets>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<Compile Include="..\..\Demo\ThingsGateway.Foundation.Demo.Rcl\Components\DebugPage\DriverDebugUIBase.cs" Link="DebugPage\DriverDebugUIBase.cs" />
<Compile Include="..\..\Demo\ThingsGateway.Foundation.Demo.Rcl\Components\DebugPage\DriverDebugUIPage.razor.cs" Link="DebugPage\DriverDebugUIPage.razor.cs" />
<Compile Include="..\..\Demo\ThingsGateway.Foundation.Demo.Rcl\Components\DebugPage\SerialSessionPage.razor.cs" Link="DebugPage\SerialSessionPage.razor.cs" />
<Compile Include="..\..\Demo\ThingsGateway.Foundation.Demo.Rcl\Components\DebugPage\TcpClientPage.razor.cs" Link="DebugPage\TcpClientPage.razor.cs" />
<Compile Include="..\..\Demo\ThingsGateway.Foundation.Demo.Rcl\Components\DebugPage\TcpServerPage.razor.cs" Link="DebugPage\TcpServerPage.razor.cs" />
<Compile Include="..\..\Demo\ThingsGateway.Foundation.Demo.Rcl\Components\DebugPage\UdpSessionPage.razor.cs" Link="DebugPage\UdpSessionPage.razor.cs" />
<Content Include="..\..\Demo\ThingsGateway.Foundation.Demo.Rcl\Components\DebugPage\DriverDebugUIPage.razor" Link="DebugPage\DriverDebugUIPage.razor" />
<Content Include="..\..\Demo\ThingsGateway.Foundation.Demo.Rcl\Components\DebugPage\SerialSessionPage.razor" Link="DebugPage\SerialSessionPage.razor" />
<Content Include="..\..\Demo\ThingsGateway.Foundation.Demo.Rcl\Components\DebugPage\TcpClientPage.razor" Link="DebugPage\TcpClientPage.razor" />
<Content Include="..\..\Demo\ThingsGateway.Foundation.Demo.Rcl\Components\DebugPage\TcpServerPage.razor" Link="DebugPage\TcpServerPage.razor" />
<Content Include="..\..\Demo\ThingsGateway.Foundation.Demo.Rcl\Components\DebugPage\UdpSessionPage.razor" Link="DebugPage\UdpSessionPage.razor" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,35 @@
@*
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
*@
@using System.Net.Http
@using Microsoft.AspNetCore.Components.Forms
@using Microsoft.AspNetCore.Components.Routing
@using Microsoft.AspNetCore.Components.Web
@using Microsoft.AspNetCore.Components.Web.Virtualization
@using Microsoft.JSInterop
@using BlazorComponent
@using Masa.Blazor
@using Masa.Blazor.Presets
@using System.Net.Http.Json
@using System.IO;
@using System.Text.Json;
@using System.Reflection;
@using ThingsGateway.Components;
@using ThingsGateway.Admin.Core;
@using ThingsGateway.Foundation;
@using ThingsGateway.Foundation.Extension.String;
@using ThingsGateway.Admin.Application;
@using ThingsGateway.Core;
@using ThingsGateway.Gateway.Application;
@using ThingsGateway.Gateway.Core;

View File

@@ -1,6 +1,6 @@
<Project>
<PropertyGroup>
<Version>3.0.0.2</Version>
<Version>3.0.0.4</Version>
<LangVersion>latest</LangVersion>
<TargetFrameworks>net6.0;net7.0</TargetFrameworks>
<Authors>Diego</Authors>

View File

@@ -1,6 +1,6 @@
<Project>
<PropertyGroup>
<Version>3.0.0.2</Version>
<Version>3.0.0.4</Version>
<LangVersion>latest</LangVersion>
<ImplicitUsings>enable</ImplicitUsings>
<TargetFrameworks>net6.0;net7.0</TargetFrameworks>

View File

@@ -28,7 +28,7 @@ namespace ThingsGateway.Admin.Application;
/// </summary>
public class OperDispatchProxy : AspectDispatchProxy, IDispatchProxy
{
static readonly Parser Parser = Parser.GetDefault();
/// <summary>
/// 服务提供器可以用来解析服务Services.GetService()
@@ -180,7 +180,8 @@ public class OperDispatchProxy : AspectDispatchProxy, IDispatchProxy
ClientInfo clientInfo = null;
if (str.HasValue)
{
clientInfo = Parser.Parse(str);
var uaParser = Parser.GetDefault();
clientInfo = uaParser.Parse(str);
}
StringBuilder stringBuilder = new();

View File

@@ -10,6 +10,7 @@
//------------------------------------------------------------------------------
#endregion
using Furion;
using Furion.Schedule;
namespace ThingsGateway.Admin.Application;
@@ -25,7 +26,7 @@ public class LogJob : IJob
public async Task ExecuteAsync(JobExecutingContext context, CancellationToken stoppingToken)
{
var db = DbContext.Db.CopyNew();
var daysAgo = 30; // 删除30天以前
var daysAgo = App.GetConfig<int?>("Logging:LogJob:DaysAgo") ?? 30;
await db.DeleteableWithAttr<SysVisitLog>().Where(u => u.CreateTime < DateTimeExtensions.CurrentDateTime.AddDays(-daysAgo)).ExecuteCommandAsync(stoppingToken); // 删除访问日志
await db.DeleteableWithAttr<SysOperateLog>().Where(u => u.CreateTime < DateTimeExtensions.CurrentDateTime.AddDays(-daysAgo)).ExecuteCommandAsync(stoppingToken); // 删除操作日志
}

View File

@@ -14,7 +14,7 @@
@namespace ThingsGateway.Admin.Blazor
@using Microsoft.AspNetCore.SignalR.Client;
@using ThingsGateway.Admin.Application;
@implements IAsyncDisposable;
@code {
HubConnection _hubConnection;
@@ -32,6 +32,7 @@
await _hubConnection.DisposeAsync();
}
}
/// <inheritdoc/>
protected override async Task OnAfterRenderAsync(bool firstRender)
{

View File

@@ -10,6 +10,7 @@
//------------------------------------------------------------------------------
#endregion
using Furion;
using Furion.Schedule;
namespace ThingsGateway.Gateway.Application;
@@ -25,7 +26,7 @@ public class BackendAndRpcLogCleanJob : IJob
public async Task ExecuteAsync(JobExecutingContext context, CancellationToken stoppingToken)
{
var db = DbContext.Db.CopyNew();
var daysAgo = 30; // 删除30天以前
var daysAgo = App.GetConfig<int?>("Logging:LogJob:DaysAgo") ?? 30;
await db.DeleteableWithAttr<BackendLog>().Where(u => u.LogTime < DateTimeExtensions.CurrentDateTime.AddDays(-daysAgo)).ExecuteCommandAsync();
await db.DeleteableWithAttr<RpcLog>().Where(u => u.LogTime < DateTimeExtensions.CurrentDateTime.AddDays(-daysAgo)).ExecuteCommandAsync();
}

View File

@@ -142,6 +142,21 @@ public class CollectDeviceWorker : BackgroundService
{
CollectDeviceThreads.Remove(devThread);
}
else
{
//单个设备重启时,注意同一线程的其他设备也会停止,需要重新初始化
foreach (var item in devThread.CollectDeviceCores)
{
item.Init(item.Device, true);
}
await devThread.StartThreadAsync();
//如果是组态更改过了,需要重新获取变量/设备运行态的值
if (isUpdateDb)
await StartOtherHostService();
}
}
else
{

View File

@@ -111,17 +111,21 @@ public class HardwareInfoWorker : BackgroundService
APPInfo.Environment = App.HostEnvironment.IsDevelopment() ? "Development" : "Production";
APPInfo.Stage = App.HostEnvironment.IsStaging() ? "Stage" : "非Stage"; // 是否Stage环境
APPInfo.UpdateTime = DateTimeExtensions.CurrentDateTime.ToDefaultDateTimeFormat();
var enable = App.GetConfig<bool?>("HardwareInfo:Enable") ?? true;
while (!stoppingToken.IsCancellationRequested)
{
try
{
APPInfo.UpdateTime = DateTimeExtensions.CurrentDateTime.ToDefaultDateTimeFormat();
APPInfo.RemoteIp = await GetIpFromOnlineAsync();
HardwareInfo?.RefreshMemoryStatus();
HardwareInfo?.RefreshMemoryList();
HardwareInfo?.RefreshNetworkAdapterList();
HardwareInfo?.RefreshCPUList();
if (enable)
{
HardwareInfo?.RefreshMemoryStatus();
HardwareInfo?.RefreshMemoryList();
HardwareInfo?.RefreshNetworkAdapterList();
HardwareInfo?.RefreshCPUList();
}
//10秒更新一次
await Task.Delay(10000, stoppingToken);
}

View File

@@ -159,6 +159,8 @@ public class UploadDeviceWorker : BackgroundService
{
item.Init(item.Device);
}
await devThread.StartThreadAsync();
}
}
else

View File

@@ -27,7 +27,6 @@ namespace ThingsGateway.Web.Core
/// </summary>
public class LoggingMonitorComponent : IServiceComponent
{
private Parser Parser = Parser.GetDefault();
/// <inheritdoc/>
public void Load(IServiceCollection services, ComponentContext componentContext)
{
@@ -42,8 +41,9 @@ namespace ThingsGateway.Web.Core
//获取头
var userAgent = httpContext.Request.Headers["User-Agent"];
if (string.IsNullOrEmpty(userAgent)) userAgent = "Other";//如果没有这个头就指定一个
var uaParser = Parser.GetDefault();
//获取客户端信息
var client = Parser.Parse(userAgent);
var client = uaParser.Parse(userAgent);
// 获取控制器/操作描述器
var controllerActionDescriptor = context.ActionDescriptor as ControllerActionDescriptor;
//操作名称默认是控制器名加方法名,自定义操作名称要在action上加Description特性

View File

@@ -1,10 +1,12 @@
{
"$schema": "https://gitee.com/dotnetchina/Furion/raw/v4/schemas/v4/furion-schema.json",
"Urls": "http://*:7200;",
"Logging": {
"LogLevel": {
"File": "Information", //程序日志写入文件等级,不包含网关日志
"BackendLog": "Information", //网关运行日志存入数据库等级,部署推荐Error
"BackendLog": "Information", //网关运行日志存入数据库等级,部署推荐Warning
"Default": "Information", //默认等级
"Console": "Information", //控制台输出日志等级
"Microsoft": "Warning",
@@ -12,33 +14,41 @@
},
"LogEnable": {
"File": true //程序日志写入文件
},
"LogJob": {
"DaysAgo": 30 //删除多少天前的日志任务会间隔24小时执行一次
}
},
//动态API设备
"DynamicApiControllerSettings": {
"LowercaseRoute": false, //是否采用小写路由bool 类型,默认 true
"KeepName": true, //是否保持原有名称不处理bool默认 false
"AsLowerCamelCase": true //启用小驼峰命名(首字母小写),默认 false
"HardwareInfo": {
"Enable": true //启用硬件信息获取,部分系统/硬件不支持相关方法设为false
},
"FriendlyExceptionSettings": {
"DefaultErrorMessage": "系统异常,请联系管理员",
"HideErrorCode": true
},
//动态API设备
"DynamicApiControllerSettings": {
"LowercaseRoute": false, //是否采用小写路由bool 类型,默认 true
"KeepName": true, //是否保持原有名称不处理bool默认 false
"AsLowerCamelCase": true //启用小驼峰命名(首字母小写),默认 false
},
//排除特定配置文件
"IgnoreConfigurationFiles": [
"Navs.json",
"sys_user.json",
"sys_config.json",
"sys_relation.json",
"sys_resource.json",
"sys_role.json",
"driver_plugin.json"
],
"FriendlyExceptionSettings": {
"DefaultErrorMessage": "系统异常,请联系管理员",
"HideErrorCode": true
},
"CorsAccessorSettings": {
"WithExposedHeaders": [ "Content-Disposition" ], // 如果前端不代理且是axios请求
"SignalRSupport": true // 启用 SignalR 跨域支持
}
}
//排除特定配置文件
"IgnoreConfigurationFiles": [
"Navs.json",
"sys_user.json",
"sys_config.json",
"sys_relation.json",
"sys_resource.json",
"sys_role.json",
"driver_plugin.json"
],
"CorsAccessorSettings": {
"WithExposedHeaders": [ "Content-Disposition" ], // 如果前端不代理且是axios请求
"SignalRSupport": true // 启用 SignalR 跨域支持
}
}

View File

@@ -3,7 +3,7 @@
<PropertyGroup>
<ApplicationIcon>wwwroot\favicon.ico</ApplicationIcon>
<ServerGarbageCollection>false</ServerGarbageCollection>
<!--<ServerGarbageCollection>false</ServerGarbageCollection>-->
<!--<PlatformTarget>x86</PlatformTarget>-->
</PropertyGroup>