修改:移入部分项目代码
This commit is contained in:
@@ -10,8 +10,8 @@
|
||||
// nuget动态加载的程序集
|
||||
"SupportPackageNamePrefixs": [
|
||||
|
||||
"ThingsGateway.Foundation.Razor",
|
||||
"ThingsGateway.Debug.Razor",
|
||||
"ThingsGateway.Admin.Application",
|
||||
"ThingsGateway.Admin.Razor",
|
||||
"ThingsGateway.Core",
|
||||
"ThingsGateway.Razor"
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<AdminVersion>7.0.1.0</AdminVersion>
|
||||
<PluginVersion>9.0.1.0</PluginVersion>
|
||||
<ProPluginVersion>9.0.1.0</ProPluginVersion>
|
||||
<PluginVersion>9.0.1.1</PluginVersion>
|
||||
<ProPluginVersion>9.0.1.1</ProPluginVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
|
||||
@@ -0,0 +1,134 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
|
||||
using CSScripting;
|
||||
|
||||
using CSScriptLib;
|
||||
|
||||
using ThingsGateway.NewLife.Caching;
|
||||
using ThingsGateway.NewLife.Threading;
|
||||
|
||||
namespace ThingsGateway.Gateway.Application;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 脚本扩展方法
|
||||
/// </summary>
|
||||
public static class CSharpScriptEngineExtension
|
||||
{
|
||||
private static string CacheKey = $"{nameof(CSharpScriptEngineExtension)}-{nameof(Do)}";
|
||||
|
||||
private static object m_waiterLock = new object();
|
||||
|
||||
/// <summary>清理计时器</summary>
|
||||
private static TimerX? _clearTimer;
|
||||
static CSharpScriptEngineExtension()
|
||||
{
|
||||
if (_clearTimer == null)
|
||||
{
|
||||
_clearTimer = new TimerX(RemoveNotAlive, null, 30 * 1000, 60 * 1000) { Async = true };
|
||||
}
|
||||
}
|
||||
|
||||
private static void RemoveNotAlive(Object? state)
|
||||
{
|
||||
//检测缓存
|
||||
try
|
||||
{
|
||||
var data = Instance.GetAll();
|
||||
lock (m_waiterLock)
|
||||
{
|
||||
|
||||
foreach (var item in data)
|
||||
{
|
||||
if (item.Value!.ExpiredTime < item.Value.VisitTime + 1800_000)
|
||||
{
|
||||
Instance.Remove(item.Key);
|
||||
item.Value?.Value?.GetType().Assembly.Unload();
|
||||
GC.Collect();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static MemoryCache Instance { get; set; } = new MemoryCache();
|
||||
|
||||
/// <summary>
|
||||
/// 执行脚本获取返回值
|
||||
/// </summary>
|
||||
public static T Do<T>(string source, params Assembly[] assemblies) where T : class
|
||||
{
|
||||
var field = $"{CacheKey}-{source}";
|
||||
var runScript = Instance.Get<T>(field);
|
||||
if (runScript == null)
|
||||
{
|
||||
lock (m_waiterLock)
|
||||
{
|
||||
runScript = Instance.Get<T>(field);
|
||||
if (runScript == null)
|
||||
{
|
||||
|
||||
var src = source.Split(Environment.NewLine.ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
|
||||
var _using = new StringBuilder();
|
||||
var _body = new StringBuilder();
|
||||
src.ToList().ForEach(l =>
|
||||
{
|
||||
if (l.StartsWith("using "))
|
||||
{
|
||||
_using.AppendLine(l);
|
||||
}
|
||||
else
|
||||
{
|
||||
_body.AppendLine(l);
|
||||
}
|
||||
|
||||
});
|
||||
var evaluator = CSScript.Evaluator;
|
||||
foreach (var item in assemblies)
|
||||
{
|
||||
evaluator = evaluator.ReferenceAssembly(item.Location);
|
||||
}
|
||||
// 动态加载并执行代码
|
||||
runScript = evaluator.With(eval => eval.IsAssemblyUnloadingEnabled = true).LoadCode<T>(
|
||||
$@"
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using ThingsGateway.Gateway.Application;
|
||||
using ThingsGateway.NewLife;
|
||||
using ThingsGateway.NewLife.Extension;
|
||||
using ThingsGateway.Gateway.Application.Extensions;
|
||||
{_using}
|
||||
{_body}
|
||||
");
|
||||
GC.Collect();
|
||||
Instance.Set(field, runScript);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Instance.SetExpire(field, TimeSpan.FromHours(1));
|
||||
|
||||
return runScript;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,170 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using CSScripting;
|
||||
|
||||
using CSScriptLib;
|
||||
|
||||
using System.Text;
|
||||
|
||||
using ThingsGateway.NewLife.Caching;
|
||||
using ThingsGateway.NewLife.Threading;
|
||||
|
||||
namespace ThingsGateway.Gateway.Application.Extensions;
|
||||
|
||||
/// <summary>
|
||||
/// 读写表达式脚本
|
||||
/// </summary>
|
||||
public interface ReadWriteExpressions
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取新值
|
||||
/// </summary>
|
||||
/// <param name="a"></param>
|
||||
/// <returns></returns>
|
||||
object GetNewValue(object a);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 表达式扩展
|
||||
/// </summary>
|
||||
public static class ExpressionEvaluatorExtension
|
||||
{
|
||||
private static string CacheKey = $"{nameof(ExpressionEvaluatorExtension)}-{nameof(GetReadWriteExpressions)}";
|
||||
|
||||
private static object m_waiterLock = new object();
|
||||
|
||||
/// <summary>清理计时器</summary>
|
||||
private static TimerX? _clearTimer;
|
||||
static ExpressionEvaluatorExtension()
|
||||
{
|
||||
if (_clearTimer == null)
|
||||
{
|
||||
_clearTimer = new TimerX(RemoveNotAlive, null, 30 * 1000, 60 * 1000) { Async = true };
|
||||
}
|
||||
}
|
||||
|
||||
private static void RemoveNotAlive(Object? state)
|
||||
{
|
||||
//检测缓存
|
||||
try
|
||||
{
|
||||
var data = Instance.GetAll();
|
||||
lock (m_waiterLock)
|
||||
{
|
||||
|
||||
foreach (var item in data)
|
||||
{
|
||||
if (item.Value!.ExpiredTime < item.Value.VisitTime + 1800_000)
|
||||
{
|
||||
Instance.Remove(item.Key);
|
||||
item.Value?.Value?.GetType().Assembly.Unload();
|
||||
GC.Collect();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static MemoryCache Instance { get; set; } = new MemoryCache();
|
||||
|
||||
/// <summary>
|
||||
/// 添加或获取脚本,非线程安全
|
||||
/// </summary>
|
||||
/// <param name="source"></param>
|
||||
/// <returns></returns>
|
||||
public static ReadWriteExpressions GetOrAddScript(string source)
|
||||
{
|
||||
var field = $"{CacheKey}-{source}";
|
||||
var runScript = Instance.Get<ReadWriteExpressions>(field);
|
||||
if (runScript == null)
|
||||
{
|
||||
if (!source.Contains("return"))
|
||||
{
|
||||
source = $"return {source}";//只判断简单脚本中可省略return字符串
|
||||
}
|
||||
|
||||
var src = source.Split(Environment.NewLine.ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
|
||||
var _using = new StringBuilder();
|
||||
var _body = new StringBuilder();
|
||||
src.ToList().ForEach(l =>
|
||||
{
|
||||
if (l.StartsWith("using "))
|
||||
{
|
||||
_using.AppendLine(l);
|
||||
}
|
||||
else
|
||||
{
|
||||
_body.AppendLine(l);
|
||||
}
|
||||
|
||||
});
|
||||
// 动态加载并执行代码
|
||||
runScript = CSScript.Evaluator.With(eval => eval.IsAssemblyUnloadingEnabled = true).LoadCode<ReadWriteExpressions>(
|
||||
$@"
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using ThingsGateway.Gateway.Application;
|
||||
using ThingsGateway.NewLife;
|
||||
using ThingsGateway.NewLife.Extension;
|
||||
using ThingsGateway.Gateway.Application.Extensions;
|
||||
{_using}
|
||||
public class Script:ReadWriteExpressions
|
||||
{{
|
||||
public object GetNewValue(object raw)
|
||||
{{
|
||||
{_body};
|
||||
}}
|
||||
}}
|
||||
");
|
||||
GC.Collect();
|
||||
Instance.Set(field, runScript);
|
||||
}
|
||||
return runScript;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 计算表达式:例如:(int)raw*100,raw为原始值
|
||||
/// </summary>
|
||||
public static object GetExpressionsResult(this string expressions, object rawvalue)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(expressions))
|
||||
{
|
||||
return rawvalue;
|
||||
}
|
||||
var readWriteExpressions = GetReadWriteExpressions(expressions);
|
||||
var value = readWriteExpressions.GetNewValue(rawvalue);
|
||||
return value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 执行脚本获取返回值ReadWriteExpressions
|
||||
/// </summary>
|
||||
public static ReadWriteExpressions GetReadWriteExpressions(string source)
|
||||
{
|
||||
var field = $"{CacheKey}-{source}";
|
||||
var runScript = Instance.Get<ReadWriteExpressions>(field);
|
||||
if (runScript == null)
|
||||
{
|
||||
lock (m_waiterLock)
|
||||
{
|
||||
runScript = GetOrAddScript(source);
|
||||
}
|
||||
}
|
||||
Instance.SetExpire(field, TimeSpan.FromHours(1));
|
||||
|
||||
return runScript;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Import Project="$(SolutionDir)PackNuget.props" />
|
||||
<Import Project="$(SolutionDir)FoundationVersion.props" />
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>netstandard2.0;net8.0;net6.0;</TargetFrameworks>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="CS-Script" Version="4.8.17" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="ThingsGateway.NewLife.X" Version="$(AdminVersion)" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,115 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace ThingsGateway.Debug;
|
||||
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.Extensions.Localization;
|
||||
|
||||
using ThingsGateway.Foundation;
|
||||
using ThingsGateway.Foundation.Json.Extension;
|
||||
|
||||
using TouchSocket.Core;
|
||||
|
||||
/// <summary>
|
||||
/// 调试UI
|
||||
/// </summary>
|
||||
public abstract class AdapterDebugBase : ComponentBase, IDisposable
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
~AdapterDebugBase()
|
||||
{
|
||||
this.SafeDispose();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 长度
|
||||
/// </summary>
|
||||
public int ArrayLength { get; set; } = 1;
|
||||
|
||||
/// <summary>
|
||||
/// 默认读写设备
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public IProtocol Plc { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 变量地址
|
||||
/// </summary>
|
||||
public string RegisterAddress { get; set; } = "400001";
|
||||
|
||||
/// <summary>
|
||||
/// 写入值
|
||||
/// </summary>
|
||||
public string WriteValue { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 数据类型
|
||||
/// </summary>
|
||||
protected DataTypeEnum DataType { get; set; } = DataTypeEnum.Int16;
|
||||
|
||||
[Inject]
|
||||
private IStringLocalizer<AdapterDebugBase> Localizer { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
{
|
||||
Plc?.SafeDispose();
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual async Task ReadAsync()
|
||||
{
|
||||
if (Plc != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
var data = await Plc.ReadAsync(RegisterAddress, ArrayLength, DataType);
|
||||
if (data.IsSuccess)
|
||||
{
|
||||
Plc.Logger?.LogInformation(data.Content.ToJsonNetString());
|
||||
}
|
||||
else
|
||||
{
|
||||
Plc.Logger?.Warning(data.ToString());
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Plc.Logger?.Exception(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual async Task WriteAsync()
|
||||
{
|
||||
if (Plc != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
var data = await Plc.WriteAsync(RegisterAddress, WriteValue.GetJTokenFromString(), DataType);
|
||||
if (data.IsSuccess)
|
||||
{
|
||||
Plc.Logger?.LogInformation($" {WriteValue.GetJTokenFromString()} {Localizer["WriteSuccess"]}");
|
||||
}
|
||||
else
|
||||
{
|
||||
Plc.Logger?.Warning(data.ToString());
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Plc.Logger?.Exception(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
@using Microsoft.AspNetCore.Components.Web;
|
||||
@using Microsoft.JSInterop;
|
||||
@using ThingsGateway.Extension
|
||||
@using ThingsGateway.Foundation
|
||||
@using ThingsGateway.Foundation.Json.Extension
|
||||
@using BootstrapBlazor.Components
|
||||
@namespace ThingsGateway.Debug
|
||||
@inherits AdapterDebugBase
|
||||
|
||||
<div class=@($"{ClassString} row my-2 mx-2") style="min-height:500px;height: 50%;">
|
||||
|
||||
<div class="col-12 col-md-5 h-100">
|
||||
<Tab class="h-100">
|
||||
<TabItem Text=@Localizer["CommonFunctions"]>
|
||||
@if (ShowDefaultReadWriteContent)
|
||||
{
|
||||
|
||||
<BootstrapInput title=@Plc?.GetAddressDescription() @bind-Value=@RegisterAddress
|
||||
ShowLabel="true" style="width:100%" />
|
||||
|
||||
|
||||
<div class="row mx-1 form-inline mt-2">
|
||||
|
||||
<div class="col-12 col-md-8 p-1">
|
||||
|
||||
<div class="p-1">
|
||||
|
||||
<BootstrapInputNumber @bind-Value=@ArrayLength ShowLabel="true" />
|
||||
<Select @bind-Value="@DataType" ShowLabel="true" IsPopover="true"/>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="col-12 col-md-4 p-1">
|
||||
|
||||
<Button IsAsync Color="Color.Primary" OnClick="ReadAsync">@Localizer["Read"]</Button>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<Divider />
|
||||
|
||||
<div class="row mx-1 form-inline mt-2">
|
||||
|
||||
<div class="col-12 col-md-8 p-1">
|
||||
<Textarea @bind-Value=@WriteValue ShowLabelTooltip="true"
|
||||
ShowLabel="true" />
|
||||
|
||||
</div>
|
||||
<div class="col-12 col-md-4 p-1">
|
||||
<Button IsAsync Color="Color.Primary" OnClick="WriteAsync">@Localizer["Write"]</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
}
|
||||
@if (ReadWriteContent != null)
|
||||
{
|
||||
@ReadWriteContent
|
||||
}
|
||||
</TabItem>
|
||||
|
||||
|
||||
<TabItem Text=@Localizer["SpecialFunctions"]>
|
||||
@if (ShowDefaultOtherContent)
|
||||
{
|
||||
|
||||
@foreach (var item in VariableRunTimes)
|
||||
{
|
||||
|
||||
<div class="row mx-1 form-inline mt-2">
|
||||
|
||||
<div class="col-12 col-md-8 p-1">
|
||||
|
||||
<div class="p-1">
|
||||
|
||||
<BootstrapInput @bind-Value=@item.RegisterAddress title="@Plc?.GetAddressDescription()"
|
||||
ShowLabel="true" style="width:100%" />
|
||||
|
||||
<Select @bind-Value="@item.DataType" ShowLabel="true" IsPopover />
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="col-12 col-md-4 p-1">
|
||||
|
||||
<div title=@(item.LastErrorMessage) class=@(item.IsOnline?"green--text":"red--text")>@(item.Value?.ToJsonNetString())</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
<Divider />
|
||||
|
||||
|
||||
<Button IsAsync Color="Color.Primary" OnClick="MulRead">@Localizer["MulRead"]</Button>
|
||||
|
||||
|
||||
}
|
||||
|
||||
</TabItem>
|
||||
|
||||
</Tab>
|
||||
|
||||
</div>
|
||||
<div class="col-12 col-md-7 ">
|
||||
@if (Plc?.Logger != null || Logger!=null)
|
||||
{
|
||||
<LogConsole Logger=@(Plc?.Logger??Logger) LogPath=@LogPath HeaderText=@HeaderText></LogConsole>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,129 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.Extensions.Localization;
|
||||
|
||||
using ThingsGateway.Foundation;
|
||||
|
||||
using TouchSocket.Core;
|
||||
|
||||
namespace ThingsGateway.Debug;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public partial class AdapterDebugComponent : AdapterDebugBase
|
||||
{
|
||||
/// <summary>
|
||||
/// MaxPack
|
||||
/// </summary>
|
||||
public int MaxPack = 100;
|
||||
|
||||
/// <summary>
|
||||
/// VariableRunTimes
|
||||
/// </summary>
|
||||
public List<VariableClass> VariableRunTimes;
|
||||
|
||||
[Parameter]
|
||||
public string ClassString { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public string HeaderText { get; set; }
|
||||
|
||||
[Parameter, EditorRequired]
|
||||
public string LogPath { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public ILog Logger { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 自定义模板
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public RenderFragment OtherContent { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 自定义模板
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public RenderFragment ReadWriteContent { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public bool ShowDefaultOtherContent { get; set; } = true;
|
||||
|
||||
[Parameter]
|
||||
public bool ShowDefaultReadWriteContent { get; set; } = true;
|
||||
|
||||
[Inject]
|
||||
private IStringLocalizer<AdapterDebugComponent> Localizer { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// MulReadAsync
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public async Task MulRead()
|
||||
{
|
||||
if (Plc != null)
|
||||
{
|
||||
var deviceVariableSourceReads = Plc.LoadSourceRead<VariableSourceClass>(VariableRunTimes, MaxPack, 1000);
|
||||
foreach (var item in deviceVariableSourceReads)
|
||||
{
|
||||
var result = await Plc.ReadAsync(item.RegisterAddress, item.Length);
|
||||
if (result.IsSuccess)
|
||||
{
|
||||
try
|
||||
{
|
||||
var result1 = item.VariableRunTimes.PraseStructContent(Plc, result.Content, exWhenAny: true);
|
||||
if (!result1.IsSuccess)
|
||||
{
|
||||
item.LastErrorMessage = result1.ErrorMessage;
|
||||
var time = DateTime.Now;
|
||||
item.VariableRunTimes.ForEach(a => a.SetValue(null, time, isOnline: false));
|
||||
Plc.Logger?.Warning(result1.ToString());
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Plc.Logger?.Exception(ex);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
item.LastErrorMessage = result.ErrorMessage;
|
||||
var time = DateTime.Now;
|
||||
item.VariableRunTimes.ForEach(a => a.SetValue(null, time, isOnline: false));
|
||||
Plc.Logger?.Warning(result.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
VariableRunTimes = new()
|
||||
{
|
||||
new VariableClass()
|
||||
{
|
||||
DataType=DataTypeEnum.Int16,
|
||||
RegisterAddress="40001",
|
||||
IntervalTime=1000,
|
||||
},
|
||||
new VariableClass()
|
||||
{
|
||||
DataType=DataTypeEnum.Int32,
|
||||
RegisterAddress="40011",
|
||||
IntervalTime=1000,
|
||||
},
|
||||
};
|
||||
|
||||
HeaderText = Localizer[nameof(HeaderText)];
|
||||
base.OnInitialized();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
@namespace ThingsGateway.Debug
|
||||
@using Microsoft.AspNetCore.Components.Web;
|
||||
@using System.IO.Ports;
|
||||
@using ThingsGateway.Foundation
|
||||
@using TouchSocket.Core
|
||||
@using BootstrapBlazor.Components
|
||||
|
||||
<Card>
|
||||
<BodyTemplate>
|
||||
<ValidateForm Model="Model" OnValidSubmit="ValidSubmit">
|
||||
|
||||
<EditorForm class="p-2" AutoGenerateAllItem="false" RowType=RowType.Inline ItemsPerRow=3 LabelWidth=100 Model="Model" >
|
||||
|
||||
<FieldItems>
|
||||
|
||||
<EditorItem @bind-Field="@context.ChannelType">
|
||||
|
||||
<EditTemplate Context="value">
|
||||
<div class="col-12 col-sm-6 col-md-4">
|
||||
<Select SkipValidate="true" @bind-Value="@value.ChannelType" OnSelectedItemChanged=@((a)=>InvokeAsync(StateHasChanged)) />
|
||||
</div>
|
||||
</EditTemplate>
|
||||
|
||||
</EditorItem>
|
||||
|
||||
<EditorItem @bind-Field="@context.RemoteUrl" Ignore=@(context.ChannelType!=ChannelTypeEnum.TcpClient&&context.ChannelType!=ChannelTypeEnum.UdpSession) />
|
||||
<EditorItem @bind-Field="@context.BindUrl" Ignore=@(context.ChannelType!=ChannelTypeEnum.TcpClient&&context.ChannelType!=ChannelTypeEnum.UdpSession&&context.ChannelType!=ChannelTypeEnum.TcpService) />
|
||||
|
||||
<EditorItem @bind-Field="@context.PortName" Ignore=@(context.ChannelType!=ChannelTypeEnum.SerialPort) />
|
||||
<EditorItem @bind-Field="@context.BaudRate" Ignore=@(context.ChannelType!=ChannelTypeEnum.SerialPort) />
|
||||
<EditorItem @bind-Field="@context.DataBits" Ignore=@(context.ChannelType!=ChannelTypeEnum.SerialPort) />
|
||||
<EditorItem @bind-Field="@context.Parity" Ignore=@(context.ChannelType!=ChannelTypeEnum.SerialPort) />
|
||||
<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) />
|
||||
|
||||
</FieldItems>
|
||||
<Buttons>
|
||||
<Button ButtonType="ButtonType.Submit" IsAsync class="mx-2" Color=Color.Primary OnClick="ConfimClick">@Localizer["Confim"]</Button>
|
||||
<Button IsAsync class="mx-2" Color=Color.Primary OnClick="ConnectClick">@Localizer["Connect"]</Button>
|
||||
<Button IsAsync class="mx-2" Color=Color.Warning OnClick="DisconnectClick">@Localizer["Disconnect"]</Button>
|
||||
</Buttons>
|
||||
</EditorForm>
|
||||
|
||||
</ValidateForm>
|
||||
|
||||
</BodyTemplate>
|
||||
</Card>
|
||||
@@ -0,0 +1,144 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using BootstrapBlazor.Components;
|
||||
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.AspNetCore.Components.Forms;
|
||||
using Microsoft.Extensions.Localization;
|
||||
|
||||
using ThingsGateway.Foundation;
|
||||
using ThingsGateway.Razor;
|
||||
|
||||
using TouchSocket.Core;
|
||||
|
||||
namespace ThingsGateway.Debug;
|
||||
|
||||
public partial class ChannelDataDebugComponent : ComponentBase
|
||||
{
|
||||
[Parameter]
|
||||
public string ClassString { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public EventCallback<ChannelData> OnConnectClick { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public EventCallback<ChannelData> OnConfimClick { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public EventCallback OnDisConnectClick { get; set; }
|
||||
|
||||
private ChannelData? Model { get; set; } = new();
|
||||
|
||||
private IEnumerable<SelectedItem> ChannelDataItems { get; set; }
|
||||
|
||||
[Inject]
|
||||
private IStringLocalizer<ChannelDataDebugComponent> Localizer { get; set; }
|
||||
|
||||
[Inject]
|
||||
private ToastService ToastService { get; set; }
|
||||
|
||||
public Task ValidSubmit(EditContext editContext)
|
||||
{
|
||||
CheckInput(Model);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private void CheckInput(ChannelData input)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (input.ChannelType == ChannelTypeEnum.TcpClient)
|
||||
{
|
||||
if (string.IsNullOrEmpty(input.RemoteUrl))
|
||||
throw new(Localizer["RemoteUrlNotNull"]);
|
||||
}
|
||||
else if (input.ChannelType == ChannelTypeEnum.TcpService)
|
||||
{
|
||||
if (string.IsNullOrEmpty(input.BindUrl))
|
||||
throw new(Localizer["BindUrlNotNull"]);
|
||||
}
|
||||
else if (input.ChannelType == ChannelTypeEnum.UdpSession)
|
||||
{
|
||||
if (string.IsNullOrEmpty(input.BindUrl) && string.IsNullOrEmpty(input.RemoteUrl))
|
||||
throw new(Localizer["BindUrlOrRemoteUrlNotNull"]);
|
||||
}
|
||||
else if (input.ChannelType == ChannelTypeEnum.SerialPort)
|
||||
{
|
||||
if (string.IsNullOrEmpty(input.PortName))
|
||||
throw new(Localizer["PortNameNotNull"]);
|
||||
if (input.BaudRate == null)
|
||||
throw new(Localizer["BaudRateNotNull"]);
|
||||
if (input.DataBits == null)
|
||||
throw new(Localizer["DataBitsNotNull"]);
|
||||
if (input.Parity == null)
|
||||
throw new(Localizer["ParityNotNull"]);
|
||||
if (input.StopBits == null)
|
||||
throw new(Localizer["StopBitsNotNull"]);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new(Localizer["NotOther"]);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ToastService.Warn(ex);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task DisconnectClick()
|
||||
{
|
||||
if (Model?.Channel != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
await Model.Channel.CloseAsync(DefaultResource.Localizer["ProactivelyDisconnect", nameof(DisconnectClick)]);
|
||||
if (OnDisConnectClick.HasDelegate)
|
||||
await OnDisConnectClick.InvokeAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Model.Channel.Logger?.Exception(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ConfimClick()
|
||||
{
|
||||
try
|
||||
{
|
||||
ChannelData.CreateChannel(Model);
|
||||
if (OnConfimClick.HasDelegate)
|
||||
await OnConfimClick.InvokeAsync(Model);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Model.Channel?.Logger?.Exception(ex);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ConnectClick()
|
||||
{
|
||||
if (Model != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (Model.Channel != null)
|
||||
if (OnConnectClick.HasDelegate)
|
||||
await OnConnectClick.InvokeAsync(Model);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Model.Channel.Logger?.Exception(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
@using Microsoft.AspNetCore.Components.Web;
|
||||
@using Microsoft.AspNetCore.Components.Web.Virtualization
|
||||
@using Microsoft.JSInterop;
|
||||
@using ThingsGateway.NewLife.Threading
|
||||
@using ThingsGateway.Extension;
|
||||
@using BootstrapBlazor.Components
|
||||
@namespace ThingsGateway.Debug
|
||||
|
||||
<Card HeaderText=@HeaderText class="mt-2" style="width:100%">
|
||||
<HeaderTemplate>
|
||||
<div class="flex-fill">
|
||||
</div>
|
||||
@if (Logger!=null)
|
||||
{
|
||||
<Select @bind-Value="@Logger.LogLevel" IsPopover></Select>
|
||||
}
|
||||
<Button Color="Color.None" style="color: var(--bs-card-title-color);" Icon=@(IsPause?"fa fa-play":"fa fa-pause") OnClick="Pause" />
|
||||
<Button IsAsync Color="Color.None" style="color: var(--bs-card-title-color);" Icon=@("fa fa-sign-out") OnClick="HandleOnExportClick" />
|
||||
<Button IsAsync Color="Color.None" style="color: var(--bs-card-title-color);" Icon=@("far fa-trash-alt") OnClick="Delete" />
|
||||
|
||||
</HeaderTemplate>
|
||||
<BodyTemplate>
|
||||
<div style=@($"height:{HeightText};overflow-y:scroll")>
|
||||
<Virtualize Items="CurrentMessages??new List<LogMessage>()" Context="itemMessage" ItemSize="60" OverscanCount=2>
|
||||
<ItemContent>
|
||||
@* <Tooltip Placement="Placement.Bottom" Title=@itemMessage.Message.Substring(0, Math.Min(itemMessage.Message.Length, 500))> *@
|
||||
<div class=@(itemMessage.Level<(byte)Microsoft.Extensions.Logging.LogLevel.Information?"":
|
||||
itemMessage.Level>=(byte)Microsoft.Extensions.Logging.LogLevel.Warning? " red--text ":"green--text ")
|
||||
style="white-space: nowrap !important;overflow: hidden !important; text-overflow: ellipsis !important;"
|
||||
title=@itemMessage.Message.Substring(0, Math.Min(itemMessage.Message.Length, 500))>
|
||||
|
||||
@itemMessage.Message.Substring(0, Math.Min(itemMessage.Message.Length, 150))
|
||||
|
||||
</div>
|
||||
@* </Tooltip> *@
|
||||
</ItemContent>
|
||||
</Virtualize>
|
||||
</div>
|
||||
|
||||
</BodyTemplate>
|
||||
</Card>
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,223 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using BootstrapBlazor.Components;
|
||||
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.AspNetCore.Components.Web;
|
||||
|
||||
using System.Diagnostics;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
using ThingsGateway.Extension;
|
||||
using ThingsGateway.Foundation;
|
||||
using ThingsGateway.Razor;
|
||||
|
||||
using TouchSocket.Core;
|
||||
|
||||
namespace ThingsGateway.Debug;
|
||||
|
||||
public partial class LogConsole : IDisposable
|
||||
{
|
||||
private bool IsPause;
|
||||
|
||||
public bool Disposed { get; set; }
|
||||
|
||||
[Parameter, EditorRequired]
|
||||
public ILog Logger { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public string HeaderText { get; set; } = "Log";
|
||||
|
||||
[Parameter]
|
||||
public string HeightText { get; set; } = "400px";
|
||||
|
||||
[Parameter, EditorRequired]
|
||||
public string LogPath { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 日志
|
||||
/// </summary>
|
||||
public ICollection<LogMessage> Messages { get; set; } = new List<LogMessage>();
|
||||
|
||||
private ICollection<LogMessage> CurrentMessages => IsPause ? PauseMessagesText : Messages;
|
||||
|
||||
[Inject]
|
||||
private DownloadService DownloadService { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 暂停缓存
|
||||
/// </summary>
|
||||
private ICollection<LogMessage> PauseMessagesText { get; set; } = new List<LogMessage>();
|
||||
|
||||
[Inject]
|
||||
private IPlatformService PlatformService { get; set; }
|
||||
|
||||
[Inject]
|
||||
private ToastService ToastService { get; set; }
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Disposed = true;
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
protected async Task ExecuteAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (LogPath != null)
|
||||
{
|
||||
var files = TextFileReader.GetFiles(LogPath);
|
||||
if (files == null || files.FirstOrDefault() == null || !files.FirstOrDefault().IsSuccess)
|
||||
{
|
||||
Messages = new List<LogMessage>();
|
||||
await Task.Delay(1000);
|
||||
}
|
||||
else
|
||||
{
|
||||
await Task.Run(async () =>
|
||||
{
|
||||
Stopwatch sw = Stopwatch.StartNew();
|
||||
var result = TextFileReader.LastLog(files.FirstOrDefault().FullName, 0);
|
||||
if (result.IsSuccess)
|
||||
{
|
||||
Messages = result.Content.Where(a => a.LogLevel >= Logger?.LogLevel).Select(a => new LogMessage((int)a.LogLevel, $"{a.LogTime} - {a.Message}{(a.ExceptionString.IsNullOrWhiteSpace() ? null : $"{Environment.NewLine}{a.ExceptionString}")}")).ToList();
|
||||
}
|
||||
else
|
||||
{
|
||||
Messages = new List<LogMessage>();
|
||||
}
|
||||
sw.Stop();
|
||||
if (sw.ElapsedMilliseconds > 500)
|
||||
{
|
||||
await Task.Delay(1000);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
System.Console.WriteLine(ex);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
_ = RunTimerAsync();
|
||||
base.OnInitialized();
|
||||
}
|
||||
|
||||
private async Task Delete()
|
||||
{
|
||||
if (LogPath != null)
|
||||
{
|
||||
var files = TextFileReader.GetFiles(LogPath);
|
||||
if (files == null || files.FirstOrDefault() == null || !files.FirstOrDefault().IsSuccess)
|
||||
{
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var item in files)
|
||||
{
|
||||
if (File.Exists(item.FullName))
|
||||
{
|
||||
int error = 0;
|
||||
while (error < 3)
|
||||
{
|
||||
try
|
||||
{
|
||||
File.SetAttributes(item.FullName, FileAttributes.Normal);
|
||||
File.Delete(item.FullName);
|
||||
break;
|
||||
}
|
||||
catch
|
||||
{
|
||||
await Task.Delay(3000);
|
||||
error++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task HandleOnExportClick(MouseEventArgs args)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (IsPause)
|
||||
{
|
||||
using var memoryStream = new MemoryStream();
|
||||
using StreamWriter writer = new(memoryStream);
|
||||
foreach (var item in PauseMessagesText)
|
||||
{
|
||||
writer.WriteLine(item.Message);
|
||||
}
|
||||
writer.Flush();
|
||||
memoryStream.Seek(0, SeekOrigin.Begin);
|
||||
|
||||
// 定义文件名称规则的正则表达式模式
|
||||
string pattern = @"[\\/:*?""<>|]";
|
||||
// 使用正则表达式将不符合规则的部分替换为下划线
|
||||
string sanitizedFileName = Regex.Replace(HeaderText, pattern, "_");
|
||||
await DownloadService.DownloadFromStreamAsync(sanitizedFileName + DateTime.Now.ToFileDateTimeFormat(), memoryStream);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (PlatformService != null)
|
||||
await PlatformService.OnLogExport(LogPath);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await ToastService.Warn(ex);
|
||||
}
|
||||
}
|
||||
|
||||
private Task Pause()
|
||||
{
|
||||
IsPause = !IsPause;
|
||||
if (IsPause)
|
||||
PauseMessagesText = Messages.ToList();
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private async Task RunTimerAsync()
|
||||
{
|
||||
while (!Disposed)
|
||||
{
|
||||
try
|
||||
{
|
||||
await ExecuteAsync();
|
||||
await InvokeAsync(StateHasChanged);
|
||||
await Task.Delay(1000);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
System.Console.WriteLine(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class LogMessage
|
||||
{
|
||||
public LogMessage(int level, string message)
|
||||
{
|
||||
Level = level;
|
||||
Message = message;
|
||||
}
|
||||
|
||||
public int Level { get; set; }
|
||||
public string Message { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
{
|
||||
"ThingsGateway.Debug.ChannelDataDebugComponent": {
|
||||
"Name": "Name",
|
||||
"Name.Required": "{0} cannot be empty",
|
||||
"ChannelType": "Channel Type",
|
||||
"Enable": "Enable",
|
||||
"LogEnable": "Enable Debug Log",
|
||||
"RemoteUrl": "Remote IP Address",
|
||||
"BindUrl": "Local Bind IP Address",
|
||||
"PortName": "COM Port",
|
||||
"BaudRate": "Baud Rate",
|
||||
"DataBits": "Data Bits",
|
||||
"Parity": "Parity",
|
||||
"StopBits": "Stop Bits",
|
||||
"DtrEnable": "Dtr",
|
||||
"RtsEnable": "Rts",
|
||||
"RemoteUrlNotNull": "Remote IP Address cannot be empty",
|
||||
"BindUrlNotNull": "Local Bind IP Address cannot be empty",
|
||||
"BindUrlOrRemoteUrlNotNull": "Remote IP Address or Local Bind IP Address cannot be empty",
|
||||
"PortNameNotNull": "COM Port cannot be empty",
|
||||
"BaudRateNotNull": "Baud Rate cannot be empty",
|
||||
"DataBitsNotNull": "Data Bits can be empty",
|
||||
"ParityNotNull": "Parity cannot be empty",
|
||||
"StopBitsNotNull": "Stop Bits cannot be empty",
|
||||
"SaveChannel": "Add/Modify Channel",
|
||||
"DeleteChannel": "Delete Channel",
|
||||
"ClearChannel": "Clear Channel",
|
||||
"ExportChannel": "Export Channel",
|
||||
"ImportChannel": "Import Channel",
|
||||
"ImportNullError": "Unable to recognize",
|
||||
"NotOther": "Not supporting other channel types",
|
||||
"Connect": "Connect",
|
||||
"Confim": "Confim",
|
||||
"Disconnect": "Disconnect",
|
||||
"Channel": "Channel"
|
||||
},
|
||||
|
||||
"ThingsGateway.Debug.AdapterDebugBase": {
|
||||
"WriteSuccess": "Write Successful",
|
||||
"DefaultSend": "Direct Send",
|
||||
"Send": "Send",
|
||||
"DataType": "Data Type",
|
||||
"RegisterAddress": "Register Address",
|
||||
"ArrayLength": "Array Length",
|
||||
"WriteValue": "Write Value",
|
||||
"SendValue": "Send Raw Message"
|
||||
},
|
||||
"ThingsGateway.Debug.AdapterDebugComponent": {
|
||||
"HeaderText": "Channel Log",
|
||||
"WriteSuccess": "Write Successful",
|
||||
"DefaultSend": "Direct Send",
|
||||
"Send": "Send",
|
||||
"DataType": "Data Type",
|
||||
"RegisterAddress": "Register Address",
|
||||
"ArrayLength": "Array Length",
|
||||
"WriteValue": "Write Value",
|
||||
"SendValue": "Send Raw Message",
|
||||
"CommonFunctions": "Common Functions",
|
||||
"SpecialFunctions": "Special Functions",
|
||||
"Read": "Read",
|
||||
"Write": "Write",
|
||||
"MulRead": "Multiple Read"
|
||||
},
|
||||
"ThingsGateway.Debug.ChannelDataEditComponent": {
|
||||
"BasicInformation": "Basic Information",
|
||||
"Connection": "Connection"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
{
|
||||
"ThingsGateway.Debug.ChannelDataDebugComponent": {
|
||||
"Name": "名称",
|
||||
"Name.Required": " {0} 不可为空",
|
||||
"ChannelType": "通道类型",
|
||||
"Enable": "启用",
|
||||
"LogEnable": "启用调试日志",
|
||||
|
||||
"RemoteUrl": "远程IP地址",
|
||||
"BindUrl": "本地绑定IP地址",
|
||||
|
||||
"PortName": "COM口",
|
||||
"BaudRate": "波特率",
|
||||
"DataBits": "数据位",
|
||||
"Parity": "校验位",
|
||||
"StopBits": "停止位",
|
||||
"DtrEnable": "Dtr",
|
||||
"RtsEnable": "Rts",
|
||||
|
||||
"RemoteUrlNotNull": "远程IP地址不可为空",
|
||||
"BindUrlNotNull": "本地绑定IP地址不可为空",
|
||||
"BindUrlOrRemoteUrlNotNull": "远程IP地址或本地绑定IP地址不可为空",
|
||||
"PortNameNotNull": "COM口不可为空",
|
||||
"BaudRateNotNull": "波特率不可为空",
|
||||
"DataBitsNotNull": "数据位可为空",
|
||||
"ParityNotNull": "校验位不可为空",
|
||||
"StopBitsNotNull": "停止位不可为空",
|
||||
|
||||
"SaveChannel": "添加/修改通道",
|
||||
"DeleteChannel": "删除通道",
|
||||
"ClearChannel": "清空通道",
|
||||
"ExportChannel": "导出通道",
|
||||
"ImportChannel": "导入通道",
|
||||
|
||||
"ImportNullError": "无法识别",
|
||||
|
||||
"NotOther": "不支持其他通道类型",
|
||||
"Connect": "连接",
|
||||
"Confim": "创建",
|
||||
"Disconnect": "断开",
|
||||
"Channel": "通道"
|
||||
},
|
||||
|
||||
"ThingsGateway.Debug.AdapterDebugBase": {
|
||||
"WriteSuccess": "写入成功",
|
||||
"DefaultSend": "直接发送",
|
||||
"Send": "发送",
|
||||
"DataType": "数据类型",
|
||||
"RegisterAddress": "寄存器地址",
|
||||
"ArrayLength": "数组长度",
|
||||
"WriteValue": "写入值",
|
||||
"SendValue": "发送原始报文"
|
||||
},
|
||||
"ThingsGateway.Debug.AdapterDebugComponent": {
|
||||
"HeaderText": "通道日志",
|
||||
"WriteSuccess": "写入成功",
|
||||
"DefaultSend": "直接发送",
|
||||
"Send": "发送",
|
||||
"DataType": "数据类型",
|
||||
"RegisterAddress": "寄存器地址",
|
||||
"ArrayLength": "数组长度",
|
||||
"WriteValue": "写入值",
|
||||
"SendValue": "发送原始报文",
|
||||
"CommonFunctions": "常用功能",
|
||||
"SpecialFunctions": "特殊功能",
|
||||
"Read": "读取",
|
||||
"Write": "写入",
|
||||
"MulRead": "多读"
|
||||
},
|
||||
"ThingsGateway.Debug.ChannelDataEditComponent": {
|
||||
"BasicInformation": "基础信息",
|
||||
"Connection": "连接"
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
{
|
||||
"ThingsGateway.Debug.ChannelDataDebugComponent": {
|
||||
"Name": "名稱",
|
||||
"Name.Required": "{0} 不可為空",
|
||||
"ChannelType": "通道類型",
|
||||
"Enable": "啟用",
|
||||
"LogEnable": "啟用調試日誌",
|
||||
"RemoteUrl": "遠程IP地址",
|
||||
"BindUrl": "本地綁定IP地址",
|
||||
"PortName": "COM口",
|
||||
"BaudRate": "波特率",
|
||||
"DataBits": "數據位",
|
||||
"Parity": "校驗位",
|
||||
"StopBits": "停止位",
|
||||
"DtrEnable": "Dtr",
|
||||
"RtsEnable": "Rts",
|
||||
"RemoteUrlNotNull": "遠程IP地址不可為空",
|
||||
"BindUrlNotNull": "本地綁定IP地址不可為空",
|
||||
"BindUrlOrRemoteUrlNotNull": "遠程IP地址或本地綁定IP地址不可為空",
|
||||
"PortNameNotNull": "COM口不可為空",
|
||||
"BaudRateNotNull": "波特率不可為空",
|
||||
"DataBitsNotNull": "數據位可為空",
|
||||
"ParityNotNull": "校驗位不可為空",
|
||||
"StopBitsNotNull": "停止位不可為空",
|
||||
"SaveChannel": "添加/修改通道",
|
||||
"DeleteChannel": "刪除通道",
|
||||
"ClearChannel": "清空通道",
|
||||
"ExportChannel": "導出通道",
|
||||
"ImportChannel": "導入通道",
|
||||
"ImportNullError": "無法識別",
|
||||
"NotOther": "不支持其他通道類型",
|
||||
"Connect": "連接",
|
||||
"Confim": "創建",
|
||||
"Disconnect": "斷開",
|
||||
"Channel": "通道"
|
||||
},
|
||||
"ThingsGateway.Debug.AdapterDebugBase": {
|
||||
"WriteSuccess": "寫入成功",
|
||||
"DefaultSend": "直接發送",
|
||||
"Send": "發送",
|
||||
"DataType": "數據類型",
|
||||
"RegisterAddress": "寄存器地址",
|
||||
"ArrayLength": "數組長度",
|
||||
"WriteValue": "寫入值",
|
||||
"SendValue": "發送原始報文"
|
||||
},
|
||||
"ThingsGateway.Debug.AdapterDebugComponent": {
|
||||
"HeaderText": "通道日誌",
|
||||
"WriteSuccess": "寫入成功",
|
||||
"DefaultSend": "直接發送",
|
||||
"Send": "發送",
|
||||
"DataType": "數據類型",
|
||||
"RegisterAddress": "寄存器地址",
|
||||
"ArrayLength": "數組長度",
|
||||
"WriteValue": "寫入值",
|
||||
"SendValue": "發送原始報文",
|
||||
"CommonFunctions": "常用功能",
|
||||
"SpecialFunctions": "特殊功能",
|
||||
"Read": "讀取",
|
||||
"Write": "寫入",
|
||||
"MulRead": "多讀"
|
||||
},
|
||||
"ThingsGateway.Debug.ChannelDataEditComponent": {
|
||||
"BasicInformation": "基礎資訊",
|
||||
"Connection": "連接"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace ThingsGateway.Debug;
|
||||
|
||||
public class HybridPlatformService : IPlatformService
|
||||
{
|
||||
public Task OnLogExport(string logPath)
|
||||
{
|
||||
OpenFolder(logPath);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private void OpenFolder(string path)
|
||||
{
|
||||
// Normalize the path for the current operating system
|
||||
path = System.IO.Path.GetFullPath(path); // Ensure the path is absolute
|
||||
|
||||
if (System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(System.Runtime.InteropServices.OSPlatform.Windows))
|
||||
{
|
||||
System.Diagnostics.Process.Start("explorer.exe", path);
|
||||
}
|
||||
else if (System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(System.Runtime.InteropServices.OSPlatform.Linux))
|
||||
{
|
||||
System.Diagnostics.Process.Start("xdg-open", path);
|
||||
}
|
||||
else if (System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(System.Runtime.InteropServices.OSPlatform.OSX))
|
||||
{
|
||||
System.Diagnostics.Process.Start("open", path);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace ThingsGateway.Debug;
|
||||
|
||||
public interface IPlatformService
|
||||
{
|
||||
/// <summary>
|
||||
/// OnLogExport
|
||||
/// </summary>
|
||||
/// <param name="logPath">日志文件夹路径</param>
|
||||
/// <returns></returns>
|
||||
public Task OnLogExport(string logPath);
|
||||
}
|
||||
22
src/Foundation/ThingsGateway.Foundation.Razor/Startup.cs
Normal file
22
src/Foundation/ThingsGateway.Foundation.Razor/Startup.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace ThingsGateway.Debug;
|
||||
|
||||
[AppStartup(100000000)]
|
||||
public class Startup : AppStartup
|
||||
{
|
||||
public void ConfigureAdminApp(IServiceCollection services)
|
||||
{
|
||||
services.AddScoped<IPlatformService, HybridPlatformService>();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Razor">
|
||||
|
||||
<Import Project="$(SolutionDir)FoundationVersion.props" />
|
||||
<Import Project="$(SolutionDir)PackNuget.props" />
|
||||
|
||||
<ItemGroup>
|
||||
<Content Remove="Locales\*.json" />
|
||||
<EmbeddedResource Include="Locales\*.json">
|
||||
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
|
||||
</EmbeddedResource>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\ThingsGateway.Foundation\ThingsGateway.Foundation.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="BootstrapBlazor" Version="8.10.2" />
|
||||
<PackageReference Include="ThingsGateway.Razor" Version="$(AdminVersion)" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
<ItemGroup>
|
||||
<FrameworkReference Include="Microsoft.AspNetCore.App" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
</Project>
|
||||
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
@@ -0,0 +1,121 @@
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
|
||||
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权
|
||||
// CSDN博客:https://blog.csdn.net/qq_40374647
|
||||
// 哔哩哔哩视频:https://space.bilibili.com/94253567
|
||||
// Gitee源代码仓库:https://gitee.com/RRQM_Home
|
||||
// Github源代码仓库:https://github.com/RRQM
|
||||
// API首页:http://rrqm_home.gitee.io/touchsocket/
|
||||
// 交流QQ群:234762506
|
||||
// 感谢您的下载和使用
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
#if !NET45_OR_GREATER
|
||||
|
||||
using Microsoft.CodeAnalysis;
|
||||
|
||||
namespace ThingsGateway.Foundation;
|
||||
|
||||
internal static class Utils
|
||||
{
|
||||
public static bool IsInheritFrom(this ITypeSymbol typeSymbol, string baseType)
|
||||
{
|
||||
if (typeSymbol.ToDisplayString() == baseType)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (typeSymbol.BaseType != null)
|
||||
{
|
||||
var b = IsInheritFrom(typeSymbol.BaseType, baseType);
|
||||
if (b)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var item in typeSymbol.AllInterfaces)
|
||||
{
|
||||
var b = IsInheritFrom(item, baseType);
|
||||
if (b)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static string RenameCamelCase(this string str)
|
||||
{
|
||||
var firstChar = str[0];
|
||||
|
||||
if (firstChar == char.ToLowerInvariant(firstChar))
|
||||
{
|
||||
return str;
|
||||
}
|
||||
|
||||
var name = str.ToCharArray();
|
||||
name[0] = char.ToLowerInvariant(firstChar);
|
||||
|
||||
return new string(name);
|
||||
}
|
||||
|
||||
public static bool HasAttribute(this ISymbol symbol, INamedTypeSymbol attribute)
|
||||
{
|
||||
foreach (var attr in symbol.GetAttributes())
|
||||
{
|
||||
var attrClass = attr.AttributeClass;
|
||||
if (attrClass != null && attrClass.ToDisplayString() == attribute.ToDisplayString())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool HasAttribute(this ISymbol symbol, string attribute, out AttributeData attributeData)
|
||||
{
|
||||
foreach (var attr in symbol.GetAttributes())
|
||||
{
|
||||
var attrClass = attr.AttributeClass;
|
||||
if (attrClass != null && attrClass.ToDisplayString() == attribute)
|
||||
{
|
||||
attributeData = attr;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
attributeData = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool HasFlags(int value, int flag)
|
||||
{
|
||||
return (value & flag) == flag;
|
||||
}
|
||||
|
||||
public static bool HasReturn(this IMethodSymbol method)
|
||||
{
|
||||
if (method.ReturnsVoid || method.ReturnType.ToDisplayString() == typeof(Task).FullName)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,128 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权
|
||||
// CSDN博客:https://blog.csdn.net/qq_40374647
|
||||
// 哔哩哔哩视频:https://space.bilibili.com/94253567
|
||||
// Gitee源代码仓库:https://gitee.com/RRQM_Home
|
||||
// Github源代码仓库:https://github.com/RRQM
|
||||
// API首页:http://rrqm_home.gitee.io/touchsocket/
|
||||
// 交流QQ群:234762506
|
||||
// 感谢您的下载和使用
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
#if !NET45_OR_GREATER
|
||||
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.Text;
|
||||
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
|
||||
namespace ThingsGateway.Foundation;
|
||||
|
||||
internal sealed class VariableCodeBuilder
|
||||
{
|
||||
private readonly INamedTypeSymbol m_pluginClass;
|
||||
|
||||
public VariableCodeBuilder(INamedTypeSymbol pluginClass)
|
||||
{
|
||||
m_pluginClass = pluginClass;
|
||||
}
|
||||
|
||||
public string Prefix { get; set; }
|
||||
|
||||
public IEnumerable<string> Usings
|
||||
{
|
||||
get
|
||||
{
|
||||
yield return "using System;";
|
||||
yield return "using System.Diagnostics;";
|
||||
yield return "using ThingsGateway.Foundation;";
|
||||
yield return "using System.Threading.Tasks;";
|
||||
}
|
||||
}
|
||||
|
||||
public string GetFileName()
|
||||
{
|
||||
return m_pluginClass.ToDisplayString() + "Generator";
|
||||
}
|
||||
|
||||
public bool TryToSourceText(out SourceText sourceText)
|
||||
{
|
||||
var code = ToString();
|
||||
if (string.IsNullOrEmpty(code))
|
||||
{
|
||||
sourceText = null;
|
||||
return false;
|
||||
}
|
||||
sourceText = SourceText.From(code, Encoding.UTF8);
|
||||
return true;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
var propertys = FindPropertys().ToList();
|
||||
if (propertys.Count == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
var codeString = new StringBuilder();
|
||||
codeString.AppendLine("/*");
|
||||
codeString.AppendLine("此代码由SourceGenerator工具直接生成,非必要请不要修改此处代码");
|
||||
codeString.AppendLine("*/");
|
||||
codeString.AppendLine("#pragma warning disable");
|
||||
|
||||
foreach (var item in Usings)
|
||||
{
|
||||
codeString.AppendLine(item);
|
||||
}
|
||||
|
||||
codeString.AppendLine($"namespace {m_pluginClass.ContainingNamespace}");
|
||||
codeString.AppendLine("{");
|
||||
codeString.AppendLine($"[global::System.CodeDom.Compiler.GeneratedCode(\"ThingsGateway.Foundation\",\"{Assembly.GetExecutingAssembly().GetName().Version}\")]");
|
||||
codeString.AppendLine($"partial class {m_pluginClass.Name}");
|
||||
codeString.AppendLine("{");
|
||||
foreach (var item in propertys)
|
||||
{
|
||||
BuildMethod(codeString, item);
|
||||
}
|
||||
codeString.AppendLine("}");
|
||||
codeString.AppendLine("}");
|
||||
|
||||
return codeString.ToString();
|
||||
}
|
||||
|
||||
private void BuildMethod(StringBuilder stringBuilder, IPropertySymbol propertySymbol)
|
||||
{
|
||||
var attributeData = propertySymbol.GetAttributes().FirstOrDefault(a => a.AttributeClass.ToDisplayString() == VariableSyntaxReceiver.VariableRuntimeAttributeTypeName);
|
||||
stringBuilder.AppendLine();
|
||||
stringBuilder.AppendLine($"public ValueTask<OperResult> Write{propertySymbol.Name}Async({propertySymbol.Type} value,CancellationToken cancellationToken=default)");
|
||||
stringBuilder.AppendLine("{");
|
||||
stringBuilder.AppendLine($"return WriteValueAsync(\"{propertySymbol.Name}\",value,cancellationToken);");
|
||||
stringBuilder.AppendLine("}");
|
||||
stringBuilder.AppendLine();
|
||||
}
|
||||
|
||||
private IEnumerable<IPropertySymbol> FindPropertys()
|
||||
{
|
||||
return m_pluginClass
|
||||
.GetMembers()
|
||||
.OfType<IPropertySymbol>()
|
||||
.Where(m =>
|
||||
{
|
||||
return m.GetAttributes().Any(a => a.AttributeClass.ToDisplayString() == VariableSyntaxReceiver.VariableRuntimeAttributeTypeName);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,88 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权
|
||||
// CSDN博客:https://blog.csdn.net/qq_40374647
|
||||
// 哔哩哔哩视频:https://space.bilibili.com/94253567
|
||||
// Gitee源代码仓库:https://gitee.com/RRQM_Home
|
||||
// Github源代码仓库:https://github.com/RRQM
|
||||
// API首页:http://rrqm_home.gitee.io/touchsocket/
|
||||
// 交流QQ群:234762506
|
||||
// 感谢您的下载和使用
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
#if !NET45_OR_GREATER
|
||||
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
|
||||
namespace ThingsGateway.Foundation;
|
||||
|
||||
/// <summary>
|
||||
/// 源生成
|
||||
/// </summary>
|
||||
[Generator]
|
||||
public class VariableObjectSourceGenerator : ISourceGenerator
|
||||
{
|
||||
private string m_generatorVariableAttribute = @"
|
||||
using System;
|
||||
|
||||
namespace ThingsGateway.Foundation
|
||||
{
|
||||
/// <summary>
|
||||
/// 使用源生成变量写入方法的调用。
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Class)]
|
||||
internal class GeneratorVariableAttribute:Attribute
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Initialize(GeneratorInitializationContext context)
|
||||
{
|
||||
//Debugger.Launch();
|
||||
context.RegisterForPostInitialization(a =>
|
||||
{
|
||||
a.AddSource(nameof(m_generatorVariableAttribute), m_generatorVariableAttribute);
|
||||
});
|
||||
context.RegisterForSyntaxNotifications(() => new VariableSyntaxReceiver());
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Execute(GeneratorExecutionContext context)
|
||||
{
|
||||
var s = context.Compilation.GetMetadataReference(context.Compilation.Assembly);
|
||||
|
||||
if (context.SyntaxReceiver is VariableSyntaxReceiver receiver)
|
||||
{
|
||||
var builders = receiver
|
||||
.GetVariableObjectTypes(context.Compilation)
|
||||
.Select(i => new VariableCodeBuilder(i))
|
||||
.Distinct();
|
||||
foreach (var builder in builders)
|
||||
{
|
||||
if (builder.TryToSourceText(out var sourceText))
|
||||
{
|
||||
var tree = CSharpSyntaxTree.ParseText(sourceText);
|
||||
var root = tree.GetRoot().NormalizeWhitespace();
|
||||
var ret = root.ToFullString();
|
||||
context.AddSource($"{builder.GetFileName()}.g.cs", ret);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,116 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权
|
||||
// CSDN博客:https://blog.csdn.net/qq_40374647
|
||||
// 哔哩哔哩视频:https://space.bilibili.com/94253567
|
||||
// Gitee源代码仓库:https://gitee.com/RRQM_Home
|
||||
// Github源代码仓库:https://github.com/RRQM
|
||||
// API首页:http://rrqm_home.gitee.io/touchsocket/
|
||||
// 交流QQ群:234762506
|
||||
// 感谢您的下载和使用
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
#if !NET45_OR_GREATER
|
||||
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
|
||||
namespace ThingsGateway.Foundation;
|
||||
|
||||
internal sealed class VariableSyntaxReceiver : ISyntaxReceiver
|
||||
{
|
||||
public const string GeneratorVariableAttributeTypeName = "ThingsGateway.Foundation.GeneratorVariableAttribute";
|
||||
public const string VariableRuntimeAttributeTypeName = "ThingsGateway.Foundation.VariableRuntimeAttribute";
|
||||
|
||||
/// <summary>
|
||||
/// 接口列表
|
||||
/// </summary>
|
||||
private readonly List<ClassDeclarationSyntax> m_classSyntaxList = new List<ClassDeclarationSyntax>();
|
||||
|
||||
/// <summary>
|
||||
/// 访问语法树
|
||||
/// </summary>
|
||||
/// <param name="syntaxNode"></param>
|
||||
void ISyntaxReceiver.OnVisitSyntaxNode(SyntaxNode syntaxNode)
|
||||
{
|
||||
if (syntaxNode is ClassDeclarationSyntax syntax)
|
||||
{
|
||||
m_classSyntaxList.Add(syntax);
|
||||
}
|
||||
}
|
||||
|
||||
public INamedTypeSymbol GeneratorVariableAttributeAttribute { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取所有插件符号
|
||||
/// </summary>
|
||||
/// <param name="compilation"></param>
|
||||
/// <returns></returns>
|
||||
public IEnumerable<INamedTypeSymbol> GetVariableObjectTypes(Compilation compilation)
|
||||
{
|
||||
GeneratorVariableAttributeAttribute = compilation.GetTypeByMetadataName(GeneratorVariableAttributeTypeName)!;
|
||||
if (GeneratorVariableAttributeAttribute == null)
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
foreach (var classSyntax in m_classSyntaxList)
|
||||
{
|
||||
var @class = compilation.GetSemanticModel(classSyntax.SyntaxTree).GetDeclaredSymbol(classSyntax);
|
||||
if (@class != null && IsVariableObject(@class))
|
||||
{
|
||||
yield return @class;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 是否为变量类
|
||||
/// </summary>
|
||||
/// <param name="class"></param>
|
||||
/// <returns></returns>
|
||||
public bool IsVariableObject(INamedTypeSymbol @class)
|
||||
{
|
||||
if (GeneratorVariableAttributeAttribute is null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (@class.IsAbstract)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return HasAttribute(@class, GeneratorVariableAttributeAttribute);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 返回是否声明指定的特性
|
||||
/// </summary>
|
||||
/// <param name="symbol"></param>
|
||||
/// <param name="attribute"></param>
|
||||
/// <returns></returns>
|
||||
public static bool HasAttribute(ISymbol symbol, INamedTypeSymbol attribute)
|
||||
{
|
||||
foreach (var attr in symbol.GetAttributes())
|
||||
{
|
||||
var attrClass = attr.AttributeClass;
|
||||
if (attrClass != null && (attrClass.AllInterfaces.Contains(attribute) || SymbolEqualityComparer.Default.Equals(attrClass, attribute)))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,13 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>netstandard2.0;</TargetFrameworks>
|
||||
<EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
|
||||
<NoPackageAnalysis>true</NoPackageAnalysis>
|
||||
<SignAssembly>false</SignAssembly>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="3.9.0" PrivateAssets="all" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,49 @@
|
||||
param($installPath, $toolsPath, $package, $project)
|
||||
|
||||
$analyzersPaths = Join-Path (Join-Path (Split-Path -Path $toolsPath -Parent) "analyzers" ) * -Resolve
|
||||
|
||||
foreach($analyzersPath in $analyzersPaths)
|
||||
{
|
||||
# Install the language agnostic analyzers.
|
||||
if (Test-Path $analyzersPath)
|
||||
{
|
||||
foreach ($analyzerFilePath in Get-ChildItem $analyzersPath -Filter *.dll)
|
||||
{
|
||||
if($project.Object.AnalyzerReferences)
|
||||
{
|
||||
$project.Object.AnalyzerReferences.Add($analyzerFilePath.FullName)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# $project.Type gives the language name like (C# or VB.NET)
|
||||
$languageFolder = ""
|
||||
if($project.Type -eq "C#")
|
||||
{
|
||||
$languageFolder = "cs"
|
||||
}
|
||||
if($project.Type -eq "VB.NET")
|
||||
{
|
||||
$languageFolder = "vb"
|
||||
}
|
||||
if($languageFolder -eq "")
|
||||
{
|
||||
return
|
||||
}
|
||||
|
||||
foreach($analyzersPath in $analyzersPaths)
|
||||
{
|
||||
# Install language specific analyzers.
|
||||
$languageAnalyzersPath = join-path $analyzersPath $languageFolder
|
||||
if (Test-Path $languageAnalyzersPath)
|
||||
{
|
||||
foreach ($analyzerFilePath in Get-ChildItem $languageAnalyzersPath -Filter *.dll)
|
||||
{
|
||||
if($project.Object.AnalyzerReferences)
|
||||
{
|
||||
$project.Object.AnalyzerReferences.Add($analyzerFilePath.FullName)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
param($installPath, $toolsPath, $package, $project)
|
||||
|
||||
$analyzersPaths = Join-Path (Join-Path (Split-Path -Path $toolsPath -Parent) "analyzers" ) * -Resolve
|
||||
|
||||
foreach($analyzersPath in $analyzersPaths)
|
||||
{
|
||||
# Uninstall the language agnostic analyzers.
|
||||
if (Test-Path $analyzersPath)
|
||||
{
|
||||
foreach ($analyzerFilePath in Get-ChildItem $analyzersPath -Filter *.dll)
|
||||
{
|
||||
if($project.Object.AnalyzerReferences)
|
||||
{
|
||||
$project.Object.AnalyzerReferences.Remove($analyzerFilePath.FullName)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# $project.Type gives the language name like (C# or VB.NET)
|
||||
$languageFolder = ""
|
||||
if($project.Type -eq "C#")
|
||||
{
|
||||
$languageFolder = "cs"
|
||||
}
|
||||
if($project.Type -eq "VB.NET")
|
||||
{
|
||||
$languageFolder = "vb"
|
||||
}
|
||||
if($languageFolder -eq "")
|
||||
{
|
||||
return
|
||||
}
|
||||
|
||||
foreach($analyzersPath in $analyzersPaths)
|
||||
{
|
||||
# Uninstall language specific analyzers.
|
||||
$languageAnalyzersPath = join-path $analyzersPath $languageFolder
|
||||
if (Test-Path $languageAnalyzersPath)
|
||||
{
|
||||
foreach ($analyzerFilePath in Get-ChildItem $languageAnalyzersPath -Filter *.dll)
|
||||
{
|
||||
if($project.Object.AnalyzerReferences)
|
||||
{
|
||||
try
|
||||
{
|
||||
$project.Object.AnalyzerReferences.Remove($analyzerFilePath.FullName)
|
||||
}
|
||||
catch
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
global using ThingsGateway.Foundation;
|
||||
|
||||
global using TouchSocket.Core;
|
||||
@@ -0,0 +1,25 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<Import Project="$(SolutionDir)PackNuget.props" />
|
||||
<Import Project="$(SolutionDir)FoundationVersion.props" />
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>netstandard2.0;net8.0;net6.0;</TargetFrameworks>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\ThingsGateway.Foundation.SourceGenerator\ThingsGateway.Foundation.SourceGenerator.csproj" >
|
||||
<Private>false</Private>
|
||||
<IncludeAssets> none;</IncludeAssets>
|
||||
</ProjectReference>
|
||||
<PackageReference Include="ThingsGateway.Foundation" Version="9.0.1" />
|
||||
<PackageReference Include="ThingsGateway.CSScript" Version="9.0.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Include="..\ThingsGateway.Foundation.SourceGenerator\tools\*.ps1" PackagePath="tools" Pack="true" Visible="false" />
|
||||
<None Include="..\ThingsGateway.Foundation.SourceGenerator\bin\$(Configuration)\netstandard2.0\ThingsGateway.Foundation.SourceGenerator.dll" PackagePath="analyzers\dotnet\cs" Pack="true" Visible="false" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,211 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
using ThingsGateway.Gateway.Application.Extensions;
|
||||
using ThingsGateway.NewLife.Reflection;
|
||||
|
||||
namespace ThingsGateway.Foundation;
|
||||
|
||||
/// <summary>
|
||||
/// VariableObject
|
||||
/// </summary>
|
||||
public abstract class VariableObject
|
||||
{
|
||||
/// <summary>
|
||||
/// 协议对象
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public IProtocol Protocol;
|
||||
|
||||
/// <summary>
|
||||
/// VariableRuntimePropertyDict
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public Dictionary<string, VariableRuntimeProperty>? VariableRuntimePropertyDict;
|
||||
|
||||
/// <summary>
|
||||
/// DeviceVariableSourceReads
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
protected List<VariableSourceClass>? DeviceVariableSourceReads;
|
||||
|
||||
/// <summary>
|
||||
/// MaxPack
|
||||
/// </summary>
|
||||
protected int MaxPack;
|
||||
|
||||
/// <summary>
|
||||
/// VariableObject
|
||||
/// </summary>
|
||||
public VariableObject(IProtocol protocol, int maxPack)
|
||||
{
|
||||
Protocol = protocol;
|
||||
MaxPack = maxPack;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ReadTime
|
||||
/// </summary>
|
||||
public DateTime ReadTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// GetExpressionsValue
|
||||
/// </summary>
|
||||
/// <param name="value"></param>
|
||||
/// <param name="variableRuntimeProperty"></param>
|
||||
/// <returns></returns>
|
||||
public virtual JToken GetExpressionsValue(object value, VariableRuntimeProperty variableRuntimeProperty)
|
||||
{
|
||||
var jToken = JToken.FromObject(value);
|
||||
if (!string.IsNullOrEmpty(variableRuntimeProperty.Attribute.WriteExpressions))
|
||||
{
|
||||
object rawdata = jToken is JValue jValue ? jValue.Value : jToken is JArray jArray ? jArray : jToken.ToString();
|
||||
|
||||
object data = variableRuntimeProperty.Attribute.WriteExpressions.GetExpressionsResult(rawdata);
|
||||
jToken = JToken.FromObject(data);
|
||||
}
|
||||
|
||||
return jToken;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// GetVariableClass
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public virtual List<VariableClass> GetVariableClass()
|
||||
{
|
||||
VariableRuntimePropertyDict ??= VariableObjectHelper.GetVariableRuntimePropertyDict(GetType());
|
||||
List<VariableClass> variableClasss = new();
|
||||
foreach (var pair in VariableRuntimePropertyDict)
|
||||
{
|
||||
var dataType = pair.Value.Attribute.DataType == DataTypeEnum.Object ? Type.GetTypeCode(pair.Value.Property.PropertyType.IsArray ? pair.Value.Property.PropertyType.GetElementType() : pair.Value.Property.PropertyType).GetDataType() : pair.Value.Attribute.DataType;
|
||||
VariableClass variableClass = new VariableClass()
|
||||
{
|
||||
DataType = dataType,
|
||||
RegisterAddress = pair.Value.Attribute.RegisterAddress,
|
||||
IntervalTime = 1000,
|
||||
};
|
||||
pair.Value.VariableClass = variableClass;
|
||||
variableClasss.Add(variableClass);
|
||||
}
|
||||
|
||||
return variableClasss;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="VariableRuntimeAttribute"/>特性连读,反射赋值到继承类中的属性
|
||||
/// </summary>
|
||||
public virtual async ValueTask<OperResult> MultiReadAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
GetVariableSources();
|
||||
//连读
|
||||
foreach (var item in DeviceVariableSourceReads)
|
||||
{
|
||||
var result = await Protocol.ReadAsync(item.RegisterAddress, item.Length, cancellationToken).ConfigureAwait(false);
|
||||
if (result.IsSuccess)
|
||||
{
|
||||
var result1 = item.VariableRunTimes.PraseStructContent(Protocol, result.Content, exWhenAny: true);
|
||||
if (!result1.IsSuccess)
|
||||
{
|
||||
item.LastErrorMessage = result1.ErrorMessage;
|
||||
var time = DateTime.Now;
|
||||
item.VariableRunTimes.ForEach(a => a.SetValue(null, time, isOnline: false));
|
||||
return new OperResult(result1);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
item.LastErrorMessage = result.ErrorMessage;
|
||||
var time = DateTime.Now;
|
||||
item.VariableRunTimes.ForEach(a => a.SetValue(null, time, isOnline: false));
|
||||
return new OperResult(result);
|
||||
}
|
||||
}
|
||||
|
||||
SetValue();
|
||||
return OperResult.Success;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new OperResult(ex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 结果反射赋值
|
||||
/// </summary>
|
||||
public virtual void SetValue()
|
||||
{
|
||||
//结果反射赋值
|
||||
foreach (var pair in VariableRuntimePropertyDict)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(pair.Value.Attribute.ReadExpressions))
|
||||
{
|
||||
var data = pair.Value.Attribute.ReadExpressions.GetExpressionsResult(pair.Value.VariableClass.Value);
|
||||
pair.Value.Property.SetValue(this, data.ChangeType(pair.Value.Property.PropertyType));
|
||||
}
|
||||
else
|
||||
{
|
||||
pair.Value.Property.SetValue(this, pair.Value.VariableClass.Value.ChangeType(pair.Value.Property.PropertyType));
|
||||
}
|
||||
}
|
||||
|
||||
ReadTime = DateTime.Now;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 写入值到设备中
|
||||
/// </summary>
|
||||
/// <param name="propertyName">属性名称,必须使用<see cref="VariableRuntimeAttribute"/>特性</param>
|
||||
/// <param name="value">写入值</param>
|
||||
/// <param name="cancellationToken">取消令箭</param>
|
||||
public virtual async ValueTask<OperResult> WriteValueAsync(string propertyName, object value, CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
GetVariableSources();
|
||||
if (string.IsNullOrEmpty(propertyName))
|
||||
{
|
||||
return new OperResult($"PropertyName cannot be null or empty.");
|
||||
}
|
||||
|
||||
if (!VariableRuntimePropertyDict.TryGetValue(propertyName, out var variableRuntimeProperty))
|
||||
{
|
||||
return new OperResult($"This attribute is not recognized and may not have been identified using the {typeof(VariableRuntimeAttribute)} attribute");
|
||||
}
|
||||
|
||||
JToken jToken = GetExpressionsValue(value, variableRuntimeProperty);
|
||||
|
||||
var result = await Protocol.WriteAsync(variableRuntimeProperty.VariableClass.RegisterAddress, jToken, variableRuntimeProperty.VariableClass.DataType, cancellationToken).ConfigureAwait(false);
|
||||
return result;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new OperResult(ex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// GetVariableSources
|
||||
/// </summary>
|
||||
protected virtual void GetVariableSources()
|
||||
{
|
||||
if (DeviceVariableSourceReads == null)
|
||||
{
|
||||
List<VariableClass> variableClasss = GetVariableClass();
|
||||
DeviceVariableSourceReads = Protocol.LoadSourceRead<VariableSourceClass>(variableClasss, MaxPack, 1000);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using System.Reflection;
|
||||
|
||||
/// <summary>
|
||||
/// VariableObjectHelper
|
||||
/// </summary>
|
||||
public static class VariableObjectHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// GetVariableRuntimePropertyDict
|
||||
/// </summary>
|
||||
/// <param name="type"></param>
|
||||
/// <returns></returns>
|
||||
public static Dictionary<string, VariableRuntimeProperty> GetVariableRuntimePropertyDict(Type type)
|
||||
{
|
||||
var properties = type.GetProperties(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
|
||||
var dictionary = new Dictionary<string, VariableRuntimeProperty>();
|
||||
foreach (var propertyInfo in properties)
|
||||
{
|
||||
VariableRuntimeAttribute variableRuntimeAttribute = propertyInfo.GetCustomAttribute<VariableRuntimeAttribute>();
|
||||
if (variableRuntimeAttribute == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
dictionary.Add(propertyInfo.Name, new VariableRuntimeProperty(variableRuntimeAttribute, propertyInfo));
|
||||
}
|
||||
|
||||
return dictionary;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace ThingsGateway.Foundation;
|
||||
|
||||
/// <summary>
|
||||
/// 变量特性
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Property)]
|
||||
public class VariableRuntimeAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// 数据类型,默认不填时会使用属性的Type
|
||||
/// </summary>
|
||||
public DataTypeEnum DataType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 读取表达式
|
||||
/// </summary>
|
||||
public string? ReadExpressions { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 寄存器地址
|
||||
/// </summary>
|
||||
public string? RegisterAddress { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 写入表达式
|
||||
/// </summary>
|
||||
public string? WriteExpressions { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using System.Reflection;
|
||||
|
||||
namespace ThingsGateway.Foundation;
|
||||
|
||||
/// <summary>
|
||||
/// VariableRuntimeProperty
|
||||
/// </summary>
|
||||
public class VariableRuntimeProperty
|
||||
{
|
||||
/// <summary>
|
||||
/// VariableRuntimeProperty
|
||||
/// </summary>
|
||||
/// <param name="attribute"></param>
|
||||
/// <param name="property"></param>
|
||||
public VariableRuntimeProperty(VariableRuntimeAttribute attribute, PropertyInfo property)
|
||||
{
|
||||
Attribute = attribute;
|
||||
Property = property;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attribute
|
||||
/// </summary>
|
||||
public VariableRuntimeAttribute Attribute { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Property
|
||||
/// </summary>
|
||||
public PropertyInfo Property { get; }
|
||||
|
||||
/// <summary>
|
||||
/// VariableClass
|
||||
/// </summary>
|
||||
public VariableClass VariableClass { get; set; }
|
||||
}
|
||||
135
src/Foundation/ThingsGateway.Foundation/Channel/ChannelData.cs
Normal file
135
src/Foundation/ThingsGateway.Foundation/Channel/ChannelData.cs
Normal file
@@ -0,0 +1,135 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using Newtonsoft.Json;
|
||||
|
||||
using System.IO.Ports;
|
||||
|
||||
using TouchSocket.SerialPorts;
|
||||
|
||||
namespace ThingsGateway.Foundation;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public class ChannelData
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public long Id { get; set; } = IncrementCount.GetCurrentValue();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual ChannelTypeEnum ChannelType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 远程地址,可由<see cref="IPHost.IPHost(string)"/>与<see href="IPHost.ToString()"/>相互转化
|
||||
/// </summary>
|
||||
public string? RemoteUrl { get; set; } = "127.0.0.1:502";
|
||||
|
||||
/// <summary>
|
||||
/// 本地地址,可由<see cref="IPHost.IPHost(string)"/>与<see href="IPHost.ToString()"/>相互转化
|
||||
/// </summary>
|
||||
public string? BindUrl { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// COM
|
||||
/// </summary>
|
||||
public string? PortName { get; set; } = "COM1";
|
||||
|
||||
/// <summary>
|
||||
/// 波特率
|
||||
/// </summary>
|
||||
public int? BaudRate { get; set; } = 9600;
|
||||
|
||||
/// <summary>
|
||||
/// 数据位
|
||||
/// </summary>
|
||||
public int? DataBits { get; set; } = 8;
|
||||
|
||||
/// <summary>
|
||||
/// 校验位
|
||||
/// </summary>
|
||||
public Parity? Parity { get; set; } = System.IO.Ports.Parity.None;
|
||||
|
||||
/// <summary>
|
||||
/// 停止位
|
||||
/// </summary>
|
||||
public StopBits? StopBits { get; set; } = System.IO.Ports.StopBits.One;
|
||||
|
||||
/// <summary>
|
||||
/// DtrEnable
|
||||
/// </summary>
|
||||
public bool? DtrEnable { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// RtsEnable
|
||||
/// </summary>
|
||||
public bool? RtsEnable { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// TouchSocketConfig
|
||||
/// </summary>
|
||||
#if NET6_0_OR_GREATER
|
||||
|
||||
[System.Text.Json.Serialization.JsonIgnore]
|
||||
#endif
|
||||
|
||||
[JsonIgnore]
|
||||
public TouchSocketConfig TouchSocketConfig;
|
||||
|
||||
/// <summary>
|
||||
/// Channel
|
||||
/// </summary>
|
||||
#if NET6_0_OR_GREATER
|
||||
|
||||
[System.Text.Json.Serialization.JsonIgnore]
|
||||
#endif
|
||||
|
||||
[JsonIgnore]
|
||||
public IChannel Channel;
|
||||
|
||||
private static IncrementCount IncrementCount = new(long.MaxValue);
|
||||
|
||||
/// <summary>
|
||||
/// 创建通道
|
||||
/// </summary>
|
||||
/// <param name="channelData"></param>
|
||||
public static void CreateChannel(ChannelData channelData)
|
||||
{
|
||||
if (channelData.Channel != null)
|
||||
{
|
||||
channelData.Channel.Close();
|
||||
channelData.Channel.SafeDispose();
|
||||
}
|
||||
channelData.TouchSocketConfig?.Dispose();
|
||||
channelData.TouchSocketConfig = new TouchSocket.Core.TouchSocketConfig();
|
||||
var logMessage = new TouchSocket.Core.LoggerGroup() { LogLevel = TouchSocket.Core.LogLevel.Trace };
|
||||
var logger = TextFileLogger.CreateTextLogger(channelData.Id.GetDebugLogPath());
|
||||
logger.LogLevel = LogLevel.Trace;
|
||||
logMessage.AddLogger(logger);
|
||||
channelData.TouchSocketConfig.ConfigureContainer(a => a.RegisterSingleton<ILog>(logMessage));
|
||||
|
||||
switch (channelData.ChannelType)
|
||||
{
|
||||
case ChannelTypeEnum.TcpClient:
|
||||
channelData.Channel = channelData.TouchSocketConfig.GetTcpClientWithIPHost(channelData.RemoteUrl, channelData.BindUrl);
|
||||
break;
|
||||
|
||||
case ChannelTypeEnum.TcpService:
|
||||
channelData.Channel = channelData.TouchSocketConfig.GetTcpServiceWithBindIPHost(channelData.BindUrl);
|
||||
break;
|
||||
|
||||
case ChannelTypeEnum.SerialPort:
|
||||
channelData.Channel = channelData.TouchSocketConfig.GetSerialPortWithOption(channelData.Map<SerialPortOption>());
|
||||
break;
|
||||
|
||||
case ChannelTypeEnum.UdpSession:
|
||||
channelData.Channel = channelData.TouchSocketConfig.GetUdpSessionWithIPHost(channelData.RemoteUrl, channelData.BindUrl);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,201 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using ThingsGateway.Foundation.Extension.String;
|
||||
|
||||
using TouchSocket.SerialPorts;
|
||||
|
||||
namespace ThingsGateway.Foundation;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 通道扩展
|
||||
/// </summary>
|
||||
public static class ChannelConfigExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// 触发通道接收事件
|
||||
/// </summary>
|
||||
/// <param name="clientChannel">通道</param>
|
||||
/// <param name="e">接收数据</param>
|
||||
/// <param name="funcs">事件</param>
|
||||
/// <returns></returns>
|
||||
internal static async Task OnChannelReceivedEvent(this IClientChannel clientChannel, ReceivedDataEventArgs e, ChannelReceivedEventHandler funcs)
|
||||
{
|
||||
clientChannel.ThrowIfNull(nameof(IClientChannel));
|
||||
e.ThrowIfNull(nameof(ReceivedDataEventArgs));
|
||||
funcs.ThrowIfNull(nameof(ChannelReceivedEventHandler));
|
||||
|
||||
if (funcs.Count > 0)
|
||||
{
|
||||
foreach (var func in funcs)
|
||||
{
|
||||
await func.Invoke(clientChannel, e).ConfigureAwait(false);
|
||||
if (e.Handled)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 触发通道事件
|
||||
/// </summary>
|
||||
/// <param name="clientChannel">通道</param>
|
||||
/// <param name="funcs">事件</param>
|
||||
/// <returns></returns>
|
||||
internal static async Task OnChannelEvent(this IClientChannel clientChannel, ChannelEventHandler funcs)
|
||||
{
|
||||
clientChannel.ThrowIfNull(nameof(IClientChannel));
|
||||
funcs.ThrowIfNull(nameof(ChannelEventHandler));
|
||||
|
||||
if (funcs.Count > 0)
|
||||
{
|
||||
foreach (var func in funcs)
|
||||
{
|
||||
var handled = await func.Invoke(clientChannel).ConfigureAwait(false);
|
||||
if (handled)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取一个新的通道。传入通道类型,远程服务端地址,绑定地址,串口配置信息
|
||||
/// </summary>
|
||||
/// <param name="config">配置</param>
|
||||
/// <param name="channelType">通道类型</param>
|
||||
/// <param name="remoteUrl">远端IP端口配置</param>
|
||||
/// <param name="bindUrl">本地IP端口配置</param>
|
||||
/// <param name="serialPortOption">串口配置</param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="ArgumentNullException"></exception>
|
||||
public static IChannel? GetChannel(this TouchSocketConfig config, ChannelTypeEnum channelType, string? remoteUrl = default, string? bindUrl = default, SerialPortOption? serialPortOption = default)
|
||||
{
|
||||
config.ThrowIfNull(nameof(TouchSocketConfig));
|
||||
channelType.ThrowIfNull(nameof(ChannelTypeEnum));
|
||||
|
||||
|
||||
switch (channelType)
|
||||
{
|
||||
case ChannelTypeEnum.TcpClient:
|
||||
remoteUrl.ThrowIfNull(nameof(IPHost));
|
||||
return config.GetTcpClientWithIPHost(remoteUrl, bindUrl);
|
||||
|
||||
case ChannelTypeEnum.TcpService:
|
||||
bindUrl.ThrowIfNull(nameof(IPHost));
|
||||
return config.GetTcpServiceWithBindIPHost(bindUrl);
|
||||
|
||||
case ChannelTypeEnum.SerialPort:
|
||||
serialPortOption.ThrowIfNull(nameof(SerialPortOption));
|
||||
return config.GetSerialPortWithOption(serialPortOption);
|
||||
|
||||
case ChannelTypeEnum.UdpSession:
|
||||
if (string.IsNullOrEmpty(remoteUrl) && string.IsNullOrEmpty(bindUrl))
|
||||
throw new ArgumentNullException(nameof(IPHost));
|
||||
return config.GetUdpSessionWithIPHost(remoteUrl, bindUrl);
|
||||
}
|
||||
return default;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取一个新的串口通道。传入串口配置信息
|
||||
/// </summary>
|
||||
/// <param name="config">配置</param>
|
||||
/// <param name="serialPortOption">串口配置</param>
|
||||
/// <returns></returns>
|
||||
public static SerialPortChannel GetSerialPortWithOption(this TouchSocketConfig config, SerialPortOption serialPortOption)
|
||||
{
|
||||
serialPortOption.ThrowIfNull(nameof(SerialPortOption));
|
||||
config.SetSerialPortOption(serialPortOption);
|
||||
|
||||
//载入配置
|
||||
SerialPortChannel serialPortChannel = new SerialPortChannel();
|
||||
serialPortChannel.Setup(config);
|
||||
|
||||
return serialPortChannel;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取一个新的Tcp客户端通道。传入远程服务端地址和绑定地址
|
||||
/// </summary>
|
||||
/// <param name="config">配置</param>
|
||||
/// <param name="remoteUrl">远端IP端口配置</param>
|
||||
/// <param name="bindUrl">本地IP端口配置</param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="ArgumentNullException"></exception>
|
||||
public static TcpClientChannel GetTcpClientWithIPHost(this TouchSocketConfig config, string remoteUrl, string? bindUrl = default)
|
||||
{
|
||||
remoteUrl.ThrowIfNull(nameof(IPHost));
|
||||
config.SetRemoteIPHost(remoteUrl);
|
||||
if (!string.IsNullOrEmpty(bindUrl))
|
||||
config.SetBindIPHost(bindUrl);
|
||||
|
||||
//载入配置
|
||||
TcpClientChannel tcpClientChannel = new TcpClientChannel();
|
||||
tcpClientChannel.Setup(config);
|
||||
return tcpClientChannel;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取一个新的Tcp服务会话通道。传入远程服务端地址和绑定地址
|
||||
/// </summary>
|
||||
/// <param name="config">配置</param>
|
||||
/// <param name="bindUrl">本地IP端口配置</param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="ArgumentNullException"></exception>
|
||||
public static TcpServiceChannel GetTcpServiceWithBindIPHost(this TouchSocketConfig config, string bindUrl)
|
||||
{
|
||||
bindUrl.ThrowIfNull(nameof(IPHost));
|
||||
|
||||
var urls = bindUrl.SplitStringBySemicolon();
|
||||
config.SetListenIPHosts(IPHost.ParseIPHosts(urls));
|
||||
//载入配置
|
||||
TcpServiceChannel tcpServiceChannel = new TcpServiceChannel();
|
||||
tcpServiceChannel.Setup(config);
|
||||
return tcpServiceChannel;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取一个新的Udp会话通道。传入远程服务端地址和绑定地址
|
||||
/// </summary>
|
||||
/// <param name="config">配置</param>
|
||||
/// <param name="remoteUrl">远端IP端口配置</param>
|
||||
/// <param name="bindUrl">本地IP端口配置</param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="ArgumentNullException"></exception>
|
||||
public static UdpSessionChannel GetUdpSessionWithIPHost(this TouchSocketConfig config, string? remoteUrl, string? bindUrl)
|
||||
{
|
||||
if (string.IsNullOrEmpty(remoteUrl) && string.IsNullOrEmpty(bindUrl))
|
||||
throw new ArgumentNullException(nameof(IPHost));
|
||||
|
||||
if (!string.IsNullOrEmpty(remoteUrl))
|
||||
config.SetRemoteIPHost(remoteUrl);
|
||||
|
||||
if (!string.IsNullOrEmpty(bindUrl))
|
||||
config.SetBindIPHost(bindUrl);
|
||||
else
|
||||
config.SetBindIPHost(new IPHost(0));
|
||||
|
||||
//载入配置
|
||||
UdpSessionChannel udpSessionChannel = new UdpSessionChannel();
|
||||
#if NET6_0_OR_GREATER
|
||||
if (OperatingSystem.IsWindows())
|
||||
{
|
||||
config.UseUdpConnReset();
|
||||
}
|
||||
#endif
|
||||
udpSessionChannel.Setup(config);
|
||||
return udpSessionChannel;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace ThingsGateway.Foundation;
|
||||
|
||||
/// <summary>
|
||||
/// 能对适配器做配置的客户端
|
||||
/// </summary>
|
||||
public interface IAdapterObject
|
||||
{
|
||||
/// <summary>
|
||||
/// 设置数据处理适配器
|
||||
/// </summary>
|
||||
/// <param name="adapter">适配器</param>
|
||||
void SetDataHandlingAdapter(DataHandlingAdapter adapter);
|
||||
}
|
||||
95
src/Foundation/ThingsGateway.Foundation/Channel/IChannel.cs
Normal file
95
src/Foundation/ThingsGateway.Foundation/Channel/IChannel.cs
Normal file
@@ -0,0 +1,95 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace ThingsGateway.Foundation;
|
||||
|
||||
/// <summary>
|
||||
/// 通道管理
|
||||
/// </summary>
|
||||
public interface IChannel : ISetupConfigObject, IDisposable, IClosableClient
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// 接收数据事件
|
||||
/// </summary>
|
||||
public ChannelReceivedEventHandler ChannelReceived { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 通道类型
|
||||
/// </summary>
|
||||
public ChannelTypeEnum ChannelType { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 通道下的所有设备
|
||||
/// </summary>
|
||||
public ConcurrentList<IProtocol> Collects { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Online
|
||||
/// </summary>
|
||||
public bool Online { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 通道启动成功后
|
||||
/// </summary>
|
||||
public ChannelEventHandler Started { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 通道启动即将成功
|
||||
/// </summary>
|
||||
public ChannelEventHandler Starting { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 通道停止
|
||||
/// </summary>
|
||||
public ChannelEventHandler Stoped { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 通道停止前
|
||||
/// </summary>
|
||||
public ChannelEventHandler Stoping { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 关闭客户端。
|
||||
/// </summary>
|
||||
/// <param name="msg">关闭消息</param>
|
||||
public void Close(string msg);
|
||||
|
||||
/// <summary>
|
||||
/// 启动
|
||||
/// </summary>
|
||||
/// <param name="millisecondsTimeout">最大等待时间</param>
|
||||
/// <param name="token">可取消令箭</param>
|
||||
/// <exception cref="TimeoutException"></exception>
|
||||
/// <exception cref="Exception"></exception>
|
||||
public void Connect(int millisecondsTimeout = 3000, CancellationToken token = default);
|
||||
|
||||
/// <summary>
|
||||
/// 异步连接
|
||||
/// </summary>
|
||||
/// <param name="millisecondsTimeout">最大等待时间</param>
|
||||
/// <param name="token">可取消令箭</param>
|
||||
/// <exception cref="TimeoutException"></exception>
|
||||
/// <exception cref="Exception"></exception>
|
||||
public Task ConnectAsync(int millisecondsTimeout = 3000, CancellationToken token = default);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 接收事件回调类
|
||||
/// </summary>
|
||||
public class ChannelReceivedEventHandler : List<Func<IClientChannel, ReceivedDataEventArgs, Task>>
|
||||
{
|
||||
}
|
||||
/// <summary>
|
||||
/// 通道事件回调类
|
||||
/// </summary>
|
||||
public class ChannelEventHandler : List<Func<IClientChannel, Task<bool>>>
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace ThingsGateway.Foundation;
|
||||
|
||||
/// <summary>
|
||||
/// 终端通道
|
||||
/// </summary>
|
||||
public interface IClientChannel : IChannel, ISender, IClient, IClientSender, IOnlineClient, IAdapterObject
|
||||
{
|
||||
/// <summary>
|
||||
/// 当前通道的数据处理适配器
|
||||
/// </summary>
|
||||
DataHandlingAdapter ReadOnlyDataHandlingAdapter { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 通道等待池
|
||||
/// </summary>
|
||||
WaitHandlePool<MessageBase> WaitHandlePool { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 收发等待锁,对于大部分工业主从协议是必须的,一个通道一个实现
|
||||
/// </summary>
|
||||
WaitLock WaitLock { get; }
|
||||
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using ThingsGateway.Foundation.Extension.String;
|
||||
|
||||
namespace ThingsGateway.Foundation;
|
||||
|
||||
/// <inheritdoc/>
|
||||
[PluginOption(Singleton = true)]
|
||||
public class DtuPlugin : PluginBase, ITcpReceivingPlugin
|
||||
{
|
||||
/// <summary>
|
||||
/// 心跳16进制字符串
|
||||
/// </summary>
|
||||
public string HeartbeatHexString { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task OnTcpReceiving(ITcpSession client, ByteBlockEventArgs e)
|
||||
{
|
||||
var len = HeartbeatHexString.HexStringToBytes().Length;
|
||||
if (client is TcpSessionClientChannel socket && socket.Service is TcpServiceChannel tcpServiceChannel)
|
||||
{
|
||||
if (!socket.Id.StartsWith("ID="))
|
||||
{
|
||||
var id = $"ID={e.ByteBlock}";
|
||||
if (tcpServiceChannel.TryGetClient(id, out var oldClient))
|
||||
{
|
||||
try
|
||||
{
|
||||
oldClient.TryShutdown();
|
||||
await oldClient.CloseAsync().ConfigureAwait(false);
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
await socket.ResetIdAsync(id).ConfigureAwait(false);
|
||||
client.Logger?.Info(DefaultResource.Localizer["DtuConnected", id]);
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
if (!socket.Service.ClientExists(socket.Id))
|
||||
{
|
||||
try
|
||||
{
|
||||
socket.TryShutdown();
|
||||
await socket.CloseAsync().ConfigureAwait(false);
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
|
||||
await e.InvokeNext().ConfigureAwait(false);//如果本插件无法处理当前数据,请将数据转至下一个插件。
|
||||
return;
|
||||
}
|
||||
|
||||
if (len > 0)
|
||||
{
|
||||
if (HeartbeatHexString == e.ByteBlock.AsSegment(0, len).ToHexString(default))
|
||||
{
|
||||
if (DateTime.UtcNow - socket.LastSentTime.ToUniversalTime() < TimeSpan.FromMilliseconds(200))
|
||||
{
|
||||
await Task.Delay(200).ConfigureAwait(false);
|
||||
}
|
||||
//回应心跳包
|
||||
await socket.SendAsync(e.ByteBlock.AsSegment()).ConfigureAwait(false);
|
||||
e.Handled = true;
|
||||
if (socket.Logger?.LogLevel <= LogLevel.Trace)
|
||||
socket.Logger?.Trace($"{socket}- Heartbeat");
|
||||
}
|
||||
}
|
||||
}
|
||||
await e.InvokeNext().ConfigureAwait(false);//如果本插件无法处理当前数据,请将数据转至下一个插件。
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using ThingsGateway.Foundation.Extension.String;
|
||||
|
||||
namespace ThingsGateway.Foundation;
|
||||
|
||||
[PluginOption(Singleton = true)]
|
||||
internal class HeartbeatAndReceivePlugin : PluginBase, ITcpConnectedPlugin, ITcpReceivingPlugin
|
||||
{
|
||||
public string DtuId { get; set; } = "DtuId";
|
||||
public string HeartbeatHexString { get; set; } = "HeartbeatHexString";
|
||||
public int HeartbeatTime { get; set; } = 3;
|
||||
|
||||
public async Task OnTcpConnected(ITcpSession client, ConnectedEventArgs e)
|
||||
{
|
||||
if (client is ITcpSessionClient)
|
||||
{
|
||||
return;//此处可判断,如果为服务器,则不用使用心跳。
|
||||
}
|
||||
|
||||
if (DtuId.IsNullOrWhiteSpace()) return;
|
||||
|
||||
if (client is ITcpClient tcpClient)
|
||||
{
|
||||
await tcpClient.SendAsync(DtuId.ToUTF8Bytes()).ConfigureAwait(false);
|
||||
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
var failedCount = 0;
|
||||
while (true)
|
||||
{
|
||||
await Task.Delay(HeartbeatTime * 1000).ConfigureAwait(false);
|
||||
if (!client.Online)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (DateTime.UtcNow - tcpClient.LastSentTime.ToUniversalTime() < TimeSpan.FromMilliseconds(200))
|
||||
{
|
||||
await Task.Delay(200).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
await tcpClient.SendAsync(HeartbeatHexString.HexStringToBytes()).ConfigureAwait(false);
|
||||
failedCount = 0;
|
||||
}
|
||||
catch
|
||||
{
|
||||
failedCount++;
|
||||
}
|
||||
if (failedCount > 3)
|
||||
{
|
||||
await client.CloseAsync("The automatic heartbeat has failed more than 3 times and has been disconnected.").ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
await e.InvokeNext().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public async Task OnTcpReceiving(ITcpSession client, ByteBlockEventArgs e)
|
||||
{
|
||||
if (client is ITcpSessionClient)
|
||||
{
|
||||
return;//此处可判断,如果为服务器,则不用使用心跳。
|
||||
}
|
||||
|
||||
if (DtuId.IsNullOrWhiteSpace()) return;
|
||||
|
||||
if (client is ITcpClient tcpClient)
|
||||
{
|
||||
var len = HeartbeatHexString.HexStringToBytes().Length;
|
||||
if (len > 0)
|
||||
{
|
||||
if (HeartbeatHexString == e.ByteBlock.AsSegment(0, len).ToHexString(default))
|
||||
{
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
await e.InvokeNext().ConfigureAwait(false);//如果本插件无法处理当前数据,请将数据转至下一个插件。
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace ThingsGateway.Foundation;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public interface IDtu : ITcpService
|
||||
{
|
||||
/// <summary>
|
||||
/// 心跳检测(大写16进制字符串)
|
||||
/// </summary>
|
||||
public string HeartbeatHexString { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace ThingsGateway.Foundation;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public interface IDtuClient
|
||||
{
|
||||
/// <summary>
|
||||
/// DtuId
|
||||
/// </summary>
|
||||
public string DtuId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 心跳内容
|
||||
/// </summary>
|
||||
public string HeartbeatHexString { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 心跳时间
|
||||
/// </summary>
|
||||
public int HeartbeatTime { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace ThingsGateway.Foundation;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public interface ITcpService
|
||||
{
|
||||
/// <summary>
|
||||
/// 客户端连接滑动过期时间(TCP服务通道时)
|
||||
/// </summary>
|
||||
public int CheckClearTime { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace ThingsGateway.Foundation;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public static class PluginUtil
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public static Action<IPluginManager> GetDtuClientPlugin(IDtuClient dtuClient)
|
||||
{
|
||||
Action<IPluginManager> action = a => { };
|
||||
|
||||
action += a =>
|
||||
{
|
||||
var plugin = a.Add<HeartbeatAndReceivePlugin>();
|
||||
plugin.HeartbeatHexString = dtuClient.HeartbeatHexString;
|
||||
plugin.DtuId = dtuClient.DtuId;
|
||||
plugin.HeartbeatTime = dtuClient.HeartbeatTime;
|
||||
};
|
||||
return action;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public static Action<IPluginManager> GetDtuPlugin(IDtu dtu)
|
||||
{
|
||||
Action<IPluginManager> action = a => { };
|
||||
|
||||
action += a =>
|
||||
{
|
||||
a.UseCheckClear()
|
||||
.SetCheckClearType(CheckClearType.All)
|
||||
.SetTick(TimeSpan.FromSeconds(dtu.CheckClearTime))
|
||||
.SetOnClose((c, t) =>
|
||||
{
|
||||
c.TryShutdown();
|
||||
c.SafeClose($"{dtu.CheckClearTime}s Timeout");
|
||||
});
|
||||
};
|
||||
|
||||
action += a =>
|
||||
{
|
||||
var plugin = a.Add<DtuPlugin>();
|
||||
plugin.HeartbeatHexString = dtu.HeartbeatHexString;
|
||||
};
|
||||
return action;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public static Action<IPluginManager> GetTcpServicePlugin(ITcpService tcpService)
|
||||
{
|
||||
Action<IPluginManager> action = a => { };
|
||||
|
||||
action += a =>
|
||||
{
|
||||
a.UseCheckClear()
|
||||
.SetCheckClearType(CheckClearType.All)
|
||||
.SetTick(TimeSpan.FromSeconds(tcpService.CheckClearTime))
|
||||
.SetOnClose((c, t) =>
|
||||
{
|
||||
c.TryShutdown();
|
||||
c.SafeClose($"{tcpService.CheckClearTime}s Timeout");
|
||||
});
|
||||
};
|
||||
|
||||
return action;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using TouchSocket.SerialPorts;
|
||||
|
||||
namespace ThingsGateway.Foundation;
|
||||
|
||||
/// <summary>
|
||||
/// 串口通道
|
||||
/// </summary>
|
||||
public class SerialPortChannel : SerialPortClient, IClientChannel
|
||||
{
|
||||
private readonly WaitLock m_semaphoreForConnect = new WaitLock();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public SerialPortChannel()
|
||||
{
|
||||
WaitHandlePool.MaxSign = ushort.MaxValue;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ChannelReceivedEventHandler ChannelReceived { get; set; } = new();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ChannelTypeEnum ChannelType => ChannelTypeEnum.SerialPort;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ConcurrentList<IProtocol> Collects { get; } = new();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public DataHandlingAdapter ReadOnlyDataHandlingAdapter => ProtectedDataHandlingAdapter;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ChannelEventHandler Started { get; set; } = new();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ChannelEventHandler Starting { get; set; } = new();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ChannelEventHandler Stoped { get; set; } = new();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ChannelEventHandler Stoping { get; set; } = new();
|
||||
/// <summary>
|
||||
/// 等待池
|
||||
/// </summary>
|
||||
public WaitHandlePool<MessageBase> WaitHandlePool { get; } = new();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public WaitLock WaitLock { get; } = new WaitLock();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Close(string msg)
|
||||
{
|
||||
CloseAsync(msg).ConfigureAwait(false).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override async Task CloseAsync(string msg)
|
||||
{
|
||||
if (Online)
|
||||
{
|
||||
await this.OnChannelEvent(Stoping).ConfigureAwait(false);
|
||||
|
||||
await base.CloseAsync(msg).ConfigureAwait(false);
|
||||
Logger?.Debug($"{ToString()} Closed{msg}");
|
||||
|
||||
await this.OnChannelEvent(Stoped).ConfigureAwait(false);
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Connect(int millisecondsTimeout = 3000, CancellationToken token = default)
|
||||
{
|
||||
ConnectAsync(millisecondsTimeout, token).ConfigureAwait(false).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public new async Task ConnectAsync(int millisecondsTimeout, CancellationToken token)
|
||||
{
|
||||
if (!Online)
|
||||
{
|
||||
try
|
||||
{
|
||||
await m_semaphoreForConnect.WaitAsync(token).ConfigureAwait(false);
|
||||
if (!Online)
|
||||
{
|
||||
await SetupAsync(Config.Clone()).ConfigureAwait(false);
|
||||
await base.ConnectAsync(millisecondsTimeout, token).ConfigureAwait(false);
|
||||
Logger?.Debug($"{ToString()} Connected");
|
||||
await this.OnChannelEvent(Started).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
m_semaphoreForConnect.Release();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void SetDataHandlingAdapter(DataHandlingAdapter adapter)
|
||||
{
|
||||
if (adapter is SingleStreamDataHandlingAdapter singleStreamDataHandlingAdapter)
|
||||
SetAdapter(singleStreamDataHandlingAdapter);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string? ToString()
|
||||
{
|
||||
if (ProtectedMainSerialPort != null)
|
||||
return $"{ProtectedMainSerialPort.PortName}[{ProtectedMainSerialPort.BaudRate},{ProtectedMainSerialPort.DataBits},{ProtectedMainSerialPort.StopBits},{ProtectedMainSerialPort.Parity}]";
|
||||
return base.ToString();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override async Task OnSerialClosing(ClosingEventArgs e)
|
||||
{
|
||||
await this.OnChannelEvent(Stoping).ConfigureAwait(false);
|
||||
Logger?.Debug($"{ToString()} Closing{(e.Message.IsNullOrEmpty() ? string.Empty : $" -{e.Message}")}");
|
||||
await base.OnSerialClosing(e).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override async Task OnSerialConnecting(ConnectingEventArgs e)
|
||||
{
|
||||
Logger?.Debug($"{ToString()} Connecting{(e.Message.IsNullOrEmpty() ? string.Empty : $" -{e.Message}")}");
|
||||
await this.OnChannelEvent(Starting).ConfigureAwait(false);
|
||||
|
||||
|
||||
await base.OnSerialConnecting(e).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override async Task OnSerialReceived(ReceivedDataEventArgs e)
|
||||
{
|
||||
await base.OnSerialReceived(e).ConfigureAwait(false);
|
||||
await this.OnChannelReceivedEvent(e, ChannelReceived).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace ThingsGateway.Foundation;
|
||||
|
||||
/// <summary>
|
||||
/// Tcp客户端通道
|
||||
/// </summary>
|
||||
public class TcpClientChannel : TcpClient, IClientChannel
|
||||
{
|
||||
private readonly WaitLock m_semaphoreForConnect = new WaitLock();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public TcpClientChannel()
|
||||
{
|
||||
WaitHandlePool.MaxSign = ushort.MaxValue;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ChannelReceivedEventHandler ChannelReceived { get; set; } = new();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ChannelTypeEnum ChannelType => ChannelTypeEnum.TcpClient;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ConcurrentList<IProtocol> Collects { get; } = new();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public DataHandlingAdapter ReadOnlyDataHandlingAdapter => DataHandlingAdapter;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ChannelEventHandler Started { get; set; } = new();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ChannelEventHandler Starting { get; set; } = new();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ChannelEventHandler Stoped { get; set; } = new();
|
||||
/// <inheritdoc/>
|
||||
public ChannelEventHandler Stoping { get; set; } = new();
|
||||
/// <summary>
|
||||
/// 等待池
|
||||
/// </summary>
|
||||
public WaitHandlePool<MessageBase> WaitHandlePool { get; } = new();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public WaitLock WaitLock { get; } = new WaitLock();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Close(string msg)
|
||||
{
|
||||
CloseAsync(msg).ConfigureAwait(false).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override async Task CloseAsync(string msg)
|
||||
{
|
||||
if (Online)
|
||||
{
|
||||
await base.CloseAsync(msg).ConfigureAwait(false);
|
||||
Logger?.Debug($"{ToString()} Closed{msg}");
|
||||
await this.OnChannelEvent(Stoped).ConfigureAwait(false);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Connect(int millisecondsTimeout = 3000, CancellationToken token = default)
|
||||
{
|
||||
ConnectAsync(millisecondsTimeout, token).ConfigureAwait(false).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override async Task ConnectAsync(int millisecondsTimeout, CancellationToken token)
|
||||
{
|
||||
if (!Online)
|
||||
{
|
||||
try
|
||||
{
|
||||
await m_semaphoreForConnect.WaitAsync(token).ConfigureAwait(false);
|
||||
if (!Online)
|
||||
{
|
||||
await SetupAsync(Config.Clone()).ConfigureAwait(false);
|
||||
await base.ConnectAsync(millisecondsTimeout, token).ConfigureAwait(false);
|
||||
Logger?.Debug($"{ToString()} Connected");
|
||||
await this.OnChannelEvent(Started).ConfigureAwait(false);
|
||||
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
m_semaphoreForConnect.Release();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void SetDataHandlingAdapter(DataHandlingAdapter adapter)
|
||||
{
|
||||
if (adapter is SingleStreamDataHandlingAdapter singleStreamDataHandlingAdapter)
|
||||
SetAdapter(singleStreamDataHandlingAdapter);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{IP}:{Port}";
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override async Task OnTcpClosing(ClosingEventArgs e)
|
||||
{
|
||||
await this.OnChannelEvent(Stoping).ConfigureAwait(false);
|
||||
Logger?.Debug($"{ToString()} Closing{(e.Message.IsNullOrEmpty() ? string.Empty : $" -{e.Message}")}");
|
||||
|
||||
await base.OnTcpClosing(e).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override async Task OnTcpConnecting(ConnectingEventArgs e)
|
||||
{
|
||||
Logger?.Debug($"{ToString()} Connecting{(e.Message.IsNullOrEmpty() ? string.Empty : $"-{e.Message}")}");
|
||||
await this.OnChannelEvent(Starting).ConfigureAwait(false);
|
||||
await base.OnTcpConnecting(e).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override async Task OnTcpReceived(ReceivedDataEventArgs e)
|
||||
{
|
||||
await base.OnTcpReceived(e).ConfigureAwait(false);
|
||||
await this.OnChannelReceivedEvent(e, ChannelReceived).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,229 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace ThingsGateway.Foundation;
|
||||
|
||||
/// <summary>
|
||||
/// TCP服务器
|
||||
/// </summary>
|
||||
/// <typeparam name="TClient"></typeparam>
|
||||
public abstract class TcpServiceChannelBase<TClient> : TcpService<TClient>, ITcpService<TClient> where TClient : TcpSessionClientChannel, new()
|
||||
{
|
||||
private readonly WaitLock m_semaphoreForConnect = new WaitLock();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ConcurrentList<IProtocol> Collects { get; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// 停止时是否发送ShutDown
|
||||
/// </summary>
|
||||
public bool ShutDownEnable { get; set; } = true;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override async Task ClearAsync()
|
||||
{
|
||||
foreach (var client in Clients)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (ShutDownEnable)
|
||||
client.TryShutdown();
|
||||
await client.CloseAsync().ConfigureAwait(false);
|
||||
client.SafeDispose();
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
public async Task ClientDisposeAsync(string id)
|
||||
{
|
||||
if (this.TryGetClient(id, out var client))
|
||||
{
|
||||
if (ShutDownEnable)
|
||||
client.TryShutdown();
|
||||
await client.CloseAsync().ConfigureAwait(false);
|
||||
client.SafeDispose();
|
||||
}
|
||||
}
|
||||
/// <inheritdoc/>
|
||||
public override async Task StartAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
await m_semaphoreForConnect.WaitAsync().ConfigureAwait(false);
|
||||
|
||||
if (ServerState != ServerState.Running)
|
||||
{
|
||||
await base.StopAsync().ConfigureAwait(false);
|
||||
await SetupAsync(Config.Clone()).ConfigureAwait(false);
|
||||
await base.StartAsync().ConfigureAwait(false);
|
||||
if (ServerState == ServerState.Running)
|
||||
{
|
||||
Logger?.Info($"{Monitors.FirstOrDefault()?.Option.IpHost}{DefaultResource.Localizer["ServiceStarted"]}");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
await base.StartAsync().ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
m_semaphoreForConnect.Release();
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override async Task StopAsync()
|
||||
{
|
||||
if (Monitors.Any())
|
||||
{
|
||||
await ClearAsync().ConfigureAwait(false);
|
||||
var iPHost = Monitors.FirstOrDefault()?.Option.IpHost;
|
||||
await base.StopAsync().ConfigureAwait(false);
|
||||
if (!Monitors.Any())
|
||||
Logger?.Info($"{iPHost}{DefaultResource.Localizer["ServiceStoped"]}");
|
||||
}
|
||||
else
|
||||
{
|
||||
await base.StopAsync().ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string? ToString()
|
||||
{
|
||||
return Monitors.FirstOrDefault()?.Option?.IpHost.ToString();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override Task OnTcpClosed(TClient socketClient, ClosedEventArgs e)
|
||||
{
|
||||
Logger?.Debug($"{socketClient} Closed");
|
||||
return base.OnTcpClosed(socketClient, e);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override Task OnTcpClosing(TClient socketClient, ClosingEventArgs e)
|
||||
{
|
||||
Logger?.Debug($"{socketClient} Closing");
|
||||
return base.OnTcpClosing(socketClient, e);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override Task OnTcpConnected(TClient socketClient, ConnectedEventArgs e)
|
||||
{
|
||||
Logger?.Debug($"{socketClient} Connected");
|
||||
return base.OnTcpConnected(socketClient, e);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override Task OnTcpConnecting(TClient socketClient, ConnectingEventArgs e)
|
||||
{
|
||||
Logger?.Debug($"{socketClient} Connecting");
|
||||
return base.OnTcpConnecting(socketClient, e);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tcp服务器通道
|
||||
/// </summary>
|
||||
public class TcpServiceChannel : TcpServiceChannelBase<TcpSessionClientChannel>, IChannel
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public ChannelReceivedEventHandler ChannelReceived { get; set; } = new();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ChannelTypeEnum ChannelType => ChannelTypeEnum.TcpService;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool Online => ServerState == ServerState.Running;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ChannelEventHandler Started { get; set; } = new();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ChannelEventHandler Starting { get; set; } = new();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ChannelEventHandler Stoped { get; set; } = new();
|
||||
/// <inheritdoc/>
|
||||
public ChannelEventHandler Stoping { get; set; } = new();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Close(string msg)
|
||||
{
|
||||
CloseAsync(msg).ConfigureAwait(false).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Task CloseAsync(string msg)
|
||||
{
|
||||
return StopAsync();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Connect(int millisecondsTimeout = 3000, CancellationToken token = default)
|
||||
{
|
||||
ConnectAsync(millisecondsTimeout, token).ConfigureAwait(false).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Task ConnectAsync(int timeout = 3000, CancellationToken token = default)
|
||||
{
|
||||
if (token.IsCancellationRequested)
|
||||
return EasyTask.CompletedTask;
|
||||
|
||||
return StartAsync();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override TcpSessionClientChannel NewClient()
|
||||
{
|
||||
return new TcpSessionClientChannel();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override async Task OnTcpClosing(TcpSessionClientChannel socketClient, ClosingEventArgs e)
|
||||
{
|
||||
await socketClient.OnChannelEvent(Stoping).ConfigureAwait(false);
|
||||
await base.OnTcpClosing(socketClient, e);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override async Task OnTcpClosed(TcpSessionClientChannel socketClient, ClosedEventArgs e)
|
||||
{
|
||||
await socketClient.OnChannelEvent(Stoped).ConfigureAwait(false);
|
||||
await base.OnTcpClosed(socketClient, e).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override async Task OnTcpConnected(TcpSessionClientChannel socketClient, ConnectedEventArgs e)
|
||||
{
|
||||
await socketClient.OnChannelEvent(Started).ConfigureAwait(false);
|
||||
await base.OnTcpConnected(socketClient, e).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override async Task OnTcpConnecting(TcpSessionClientChannel socketClient, ConnectingEventArgs e)
|
||||
{
|
||||
await socketClient.OnChannelEvent(Starting).ConfigureAwait(false);
|
||||
await base.OnTcpConnecting(socketClient, e).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override async Task OnTcpReceived(TcpSessionClientChannel socketClient, ReceivedDataEventArgs e)
|
||||
{
|
||||
await base.OnTcpReceived(socketClient, e).ConfigureAwait(false);
|
||||
await socketClient.OnChannelReceivedEvent(e, ChannelReceived).ConfigureAwait(false);
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace ThingsGateway.Foundation;
|
||||
|
||||
/// <summary>
|
||||
/// Tcp终端通道
|
||||
/// </summary>
|
||||
public class TcpSessionClientChannel : TcpSessionClient, IClientChannel
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public TcpSessionClientChannel()
|
||||
{
|
||||
WaitHandlePool.MaxSign = ushort.MaxValue;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ChannelReceivedEventHandler ChannelReceived { get; set; } = new();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ChannelTypeEnum ChannelType => ChannelTypeEnum.TcpService;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ConcurrentList<IProtocol> Collects { get; } = new();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public DataHandlingAdapter ReadOnlyDataHandlingAdapter => DataHandlingAdapter;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ChannelEventHandler Started { get; set; } = new();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ChannelEventHandler Starting { get; set; } = new();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ChannelEventHandler Stoped { get; set; } = new();
|
||||
/// <inheritdoc/>
|
||||
public ChannelEventHandler Stoping { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// 等待池
|
||||
/// </summary>
|
||||
public WaitHandlePool<MessageBase> WaitHandlePool { get; private set; } = new();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public WaitLock WaitLock { get; } = new WaitLock();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Close(string msg)
|
||||
{
|
||||
CloseAsync(msg).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override Task CloseAsync(string msg)
|
||||
{
|
||||
WaitHandlePool.SafeDispose();
|
||||
return base.CloseAsync(msg);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Connect(int millisecondsTimeout = 3000, CancellationToken token = default) => throw new NotSupportedException();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Task ConnectAsync(int timeout, CancellationToken token) => throw new NotImplementedException();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void SetDataHandlingAdapter(DataHandlingAdapter adapter)
|
||||
{
|
||||
if (adapter is SingleStreamDataHandlingAdapter singleStreamDataHandlingAdapter)
|
||||
SetAdapter(singleStreamDataHandlingAdapter);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Task SetupAsync(TouchSocketConfig config)
|
||||
{
|
||||
return EasyTask.CompletedTask;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{IP}:{Port}:{Id}";
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (DisposedValue) return;
|
||||
WaitHandlePool.SafeDispose();
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override async Task OnTcpClosed(ClosedEventArgs e)
|
||||
{
|
||||
Logger?.Debug($"{ToString()} Closed{(e.Message.IsNullOrEmpty() ? string.Empty : $"-{e.Message}")}");
|
||||
await this.OnChannelEvent(Stoped).ConfigureAwait(false);
|
||||
await base.OnTcpClosed(e).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override async Task OnTcpClosing(ClosingEventArgs e)
|
||||
{
|
||||
Logger?.Debug($"{ToString()} Closing{(e.Message.IsNullOrEmpty() ? string.Empty : $"-{e.Message}")}");
|
||||
await this.OnChannelEvent(Stoping).ConfigureAwait(false);
|
||||
await base.OnTcpClosing(e).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override async Task OnTcpConnected(ConnectedEventArgs e)
|
||||
{
|
||||
//Logger?.Debug($"{ToString()}{FoundationConst.Connected}");
|
||||
await this.OnChannelEvent(Started).ConfigureAwait(false);
|
||||
|
||||
await base.OnTcpConnected(e).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override async Task OnTcpConnecting(ConnectingEventArgs e)
|
||||
{
|
||||
await this.OnChannelEvent(Starting).ConfigureAwait(false);
|
||||
|
||||
await base.OnTcpConnecting(e).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override async Task OnTcpReceived(ReceivedDataEventArgs e)
|
||||
{
|
||||
await base.OnTcpReceived(e).ConfigureAwait(false);
|
||||
await this.OnChannelReceivedEvent(e, ChannelReceived).ConfigureAwait(false);
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace ThingsGateway.Foundation;
|
||||
|
||||
/// <summary>
|
||||
/// Udp通道
|
||||
/// </summary>
|
||||
public class UdpSessionChannel : UdpSession, IClientChannel
|
||||
{
|
||||
private readonly WaitLock m_semaphoreForConnect = new WaitLock();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public UdpSessionChannel()
|
||||
{
|
||||
WaitHandlePool.MaxSign = ushort.MaxValue;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ChannelReceivedEventHandler ChannelReceived { get; set; } = new();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ChannelTypeEnum ChannelType => ChannelTypeEnum.UdpSession;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ConcurrentList<IProtocol> Collects { get; } = new();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool Online => ServerState == ServerState.Running;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public DataHandlingAdapter ReadOnlyDataHandlingAdapter => DataHandlingAdapter;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ChannelEventHandler Started { get; set; } = new();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ChannelEventHandler Starting { get; set; } = new();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ChannelEventHandler Stoped { get; set; } = new();
|
||||
/// <inheritdoc/>
|
||||
public ChannelEventHandler Stoping { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// 等待池
|
||||
/// </summary>
|
||||
public WaitHandlePool<MessageBase> WaitHandlePool { get; } = new();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public WaitLock WaitLock { get; } = new WaitLock();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Close(string msg)
|
||||
{
|
||||
CloseAsync(msg).ConfigureAwait(false).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Task CloseAsync(string msg)
|
||||
{
|
||||
return StopAsync();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Connect(int millisecondsTimeout = 3000, CancellationToken token = default)
|
||||
{
|
||||
ConnectAsync(millisecondsTimeout, token).ConfigureAwait(false).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task ConnectAsync(int timeout = 3000, CancellationToken token = default)
|
||||
{
|
||||
if (token.IsCancellationRequested)
|
||||
return;
|
||||
await this.OnChannelEvent(Starting).ConfigureAwait(false);
|
||||
await StartAsync().ConfigureAwait(false);
|
||||
await this.OnChannelEvent(Started).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void SetDataHandlingAdapter(DataHandlingAdapter adapter)
|
||||
{
|
||||
if (adapter is UdpDataHandlingAdapter udpDataHandlingAdapter)
|
||||
SetAdapter(udpDataHandlingAdapter);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override async Task StartAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
await m_semaphoreForConnect.WaitAsync().ConfigureAwait(false);
|
||||
|
||||
if (ServerState != ServerState.Running)
|
||||
{
|
||||
await base.StopAsync().ConfigureAwait(false);
|
||||
await SetupAsync(Config.Clone()).ConfigureAwait(false);
|
||||
await base.StartAsync().ConfigureAwait(false);
|
||||
if (ServerState == ServerState.Running)
|
||||
{
|
||||
Logger?.Info($"{Monitor.IPHost}{DefaultResource.Localizer["ServiceStarted"]}");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
await base.StartAsync().ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
m_semaphoreForConnect.Release();
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override async Task StopAsync()
|
||||
{
|
||||
if (Monitor != null)
|
||||
{
|
||||
await this.OnChannelEvent(Stoping).ConfigureAwait(false);
|
||||
await base.StopAsync().ConfigureAwait(false);
|
||||
if (Monitor == null)
|
||||
{
|
||||
await this.OnChannelEvent(Stoped).ConfigureAwait(false);
|
||||
Logger?.Info($"{DefaultResource.Localizer["ServiceStoped"]}");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
await base.StopAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string? ToString()
|
||||
{
|
||||
return RemoteIPHost?.ToString().Replace("tcp", "udp");
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override async Task OnUdpReceived(UdpReceivedDataEventArgs e)
|
||||
{
|
||||
await base.OnUdpReceived(e).ConfigureAwait(false);
|
||||
await this.OnChannelReceivedEvent(e, ChannelReceived).ConfigureAwait(false);
|
||||
|
||||
}
|
||||
}
|
||||
127
src/Foundation/ThingsGateway.Foundation/Common/IncrementCount.cs
Normal file
127
src/Foundation/ThingsGateway.Foundation/Common/IncrementCount.cs
Normal file
@@ -0,0 +1,127 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace ThingsGateway.Foundation;
|
||||
|
||||
/// <summary>
|
||||
/// 自增数据类,用于自增数据,可以设置最大值,初始值,自增步长等。
|
||||
/// </summary>
|
||||
public sealed class IncrementCount : DisposableObject
|
||||
{
|
||||
private readonly WaitLock easyLock = new();
|
||||
private long current = 0;
|
||||
private long max = long.MaxValue;
|
||||
private long start = 0;
|
||||
|
||||
/// <inheritdoc cref="IncrementCount"/>
|
||||
public IncrementCount(long max, long start = 0, int tick = 1)
|
||||
{
|
||||
this.start = start;
|
||||
this.max = max;
|
||||
current = start;
|
||||
IncreaseTick = tick;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 自增步长
|
||||
/// </summary>
|
||||
public int IncreaseTick { get; set; } = 1;
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前的计数器的最大的设置值
|
||||
/// </summary>
|
||||
public long MaxValue => max;
|
||||
|
||||
/// <summary>
|
||||
/// 获取自增信息,获得数据之后,下一次获取将会自增,如果自增后大于最大值,则会重置为最小值,如果小于最小值,则会重置为最大值。
|
||||
/// </summary>
|
||||
public long GetCurrentValue()
|
||||
{
|
||||
easyLock.Wait();
|
||||
long current = this.current;
|
||||
this.current += IncreaseTick;
|
||||
if (this.current > max)
|
||||
{
|
||||
this.current = start;
|
||||
}
|
||||
else if (this.current < start)
|
||||
{
|
||||
this.current = max;
|
||||
}
|
||||
|
||||
easyLock.Release();
|
||||
return current;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将当前的值重置为初始值。
|
||||
/// </summary>
|
||||
public void ResetCurrentValue()
|
||||
{
|
||||
easyLock.Wait();
|
||||
current = start;
|
||||
easyLock.Release();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将当前的值重置为指定值
|
||||
/// </summary>
|
||||
/// <param name="value">指定值</param>
|
||||
public void ResetCurrentValue(long value)
|
||||
{
|
||||
easyLock.Wait();
|
||||
current = value <= max ? value >= start ? value : start : max;
|
||||
easyLock.Release();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 重置当前序号的最大值
|
||||
/// </summary>
|
||||
public void ResetMaxValue(long max)
|
||||
{
|
||||
easyLock.Wait();
|
||||
if (max > start)
|
||||
{
|
||||
if (max < current)
|
||||
{
|
||||
current = start;
|
||||
}
|
||||
|
||||
this.max = max;
|
||||
}
|
||||
easyLock.Release();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 重置当前序号的初始值
|
||||
/// </summary>
|
||||
/// <param name="start">初始值</param>
|
||||
public void ResetStartValue(long start)
|
||||
{
|
||||
easyLock.Wait();
|
||||
if (start < max)
|
||||
{
|
||||
if (current < start)
|
||||
{
|
||||
current = start;
|
||||
}
|
||||
|
||||
this.start = start;
|
||||
}
|
||||
easyLock.Release();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
easyLock.SafeDispose();
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
}
|
||||
60
src/Foundation/ThingsGateway.Foundation/Common/TimeTick.cs
Normal file
60
src/Foundation/ThingsGateway.Foundation/Common/TimeTick.cs
Normal file
@@ -0,0 +1,60 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace ThingsGateway.Foundation;
|
||||
|
||||
/// <summary>
|
||||
/// 时间刻度器,最小时间间隔10毫秒
|
||||
/// </summary>
|
||||
public class TimeTick
|
||||
{
|
||||
/// <summary>
|
||||
/// 时间间隔(毫秒)
|
||||
/// </summary>
|
||||
private readonly int intervalMilliseconds = 1000;
|
||||
|
||||
/// <inheritdoc cref="TimeTick"/>
|
||||
public TimeTick(int intervalMilliseconds = 1000)
|
||||
{
|
||||
if (intervalMilliseconds < 10)
|
||||
intervalMilliseconds = 10;
|
||||
LastTime = DateTime.Now.AddMilliseconds(-intervalMilliseconds);
|
||||
this.intervalMilliseconds = intervalMilliseconds;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 上次触发时间
|
||||
/// </summary>
|
||||
public DateTime LastTime { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否触发时间刻度
|
||||
/// </summary>
|
||||
/// <param name="currentTime">当前时间</param>
|
||||
/// <returns>是否触发时间刻度</returns>
|
||||
public bool IsTickHappen(DateTime currentTime)
|
||||
{
|
||||
var nextTime = LastTime.AddMilliseconds(intervalMilliseconds);
|
||||
var diffMilliseconds = (currentTime - nextTime).TotalMilliseconds;
|
||||
if (diffMilliseconds < 0)
|
||||
return false;
|
||||
else if (diffMilliseconds > intervalMilliseconds)
|
||||
LastTime = currentTime; //选择当前时间
|
||||
else
|
||||
LastTime = nextTime;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 是否到达设置的时间间隔
|
||||
/// </summary>
|
||||
/// <returns>是否到达设置的时间间隔</returns>
|
||||
public bool IsTickHappen() => IsTickHappen(DateTime.Now);
|
||||
}
|
||||
89
src/Foundation/ThingsGateway.Foundation/Common/WaitLock.cs
Normal file
89
src/Foundation/ThingsGateway.Foundation/Common/WaitLock.cs
Normal file
@@ -0,0 +1,89 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace ThingsGateway.Foundation;
|
||||
|
||||
/// <summary>
|
||||
/// WaitLock,使用轻量级SemaphoreSlim锁,只允许一个并发量,并记录并发信息
|
||||
/// </summary>
|
||||
public sealed class WaitLock : DisposableObject
|
||||
{
|
||||
private readonly SemaphoreSlim m_waiterLock = new SemaphoreSlim(1, 1);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public WaitLock(bool initialState = true)
|
||||
{
|
||||
if (!initialState)
|
||||
m_waiterLock.Wait();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
~WaitLock()
|
||||
{
|
||||
this.SafeDispose();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 当前锁是否在等待当中
|
||||
/// </summary>
|
||||
public bool IsWaitting => m_waiterLock.CurrentCount == 0;
|
||||
|
||||
/// <summary>
|
||||
/// 离开锁
|
||||
/// </summary>
|
||||
public void Release()
|
||||
{
|
||||
lock (this)
|
||||
{
|
||||
if (IsWaitting)
|
||||
m_waiterLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 进入锁
|
||||
/// </summary>
|
||||
public void Wait(CancellationToken cancellationToken = default)
|
||||
{
|
||||
m_waiterLock.Wait(cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 进入锁
|
||||
/// </summary>
|
||||
public bool Wait(TimeSpan timeSpan, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var data = m_waiterLock.Wait(timeSpan, cancellationToken);
|
||||
return data;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 进入锁
|
||||
/// </summary>
|
||||
public Task WaitAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
return m_waiterLock.WaitAsync(cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 进入锁
|
||||
/// </summary>
|
||||
public Task<bool> WaitAsync(TimeSpan timeSpan, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return m_waiterLock.WaitAsync(timeSpan, cancellationToken);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
m_waiterLock.SafeDispose();
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace ThingsGateway.Foundation;
|
||||
|
||||
/// <summary>
|
||||
/// Json字符串转到对应类
|
||||
/// </summary>
|
||||
public class JsonStringToClassSerializerFormatter<TState> : ISerializerFormatter<string, TState>
|
||||
{
|
||||
/// <summary>
|
||||
/// JsonSettings
|
||||
/// </summary>
|
||||
public JsonSerializerSettings JsonSettings { get; set; } = new JsonSerializerSettings();
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc/>
|
||||
/// </summary>
|
||||
public int Order { get; set; } = -99;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool TryDeserialize(TState state, in string source, Type targetType, out object target)
|
||||
{
|
||||
try
|
||||
{
|
||||
target = JsonConvert.DeserializeObject(source, targetType, JsonSettings)!;
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
target = default;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool TrySerialize(TState state, in object target, out string source)
|
||||
{
|
||||
try
|
||||
{
|
||||
source = JsonConvert.SerializeObject(target, JsonSettings);
|
||||
return true;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
source = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using ThingsGateway.Foundation.Extension.String;
|
||||
|
||||
namespace ThingsGateway.Foundation;
|
||||
|
||||
/// <summary>
|
||||
/// String值转换为基础类型。
|
||||
/// </summary>
|
||||
public class StringToClassConverter<TState> : ISerializerFormatter<string, TState>
|
||||
{
|
||||
/// <summary>
|
||||
/// <inheritdoc/>
|
||||
/// </summary>
|
||||
public int Order { get; set; } = -100;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool TryDeserialize(TState state, in string source, Type targetType, out object target)
|
||||
{
|
||||
return targetType.GetTypeValue(source, out target!);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool TrySerialize(TState state, in object target, out string source)
|
||||
{
|
||||
if (target != null)
|
||||
{
|
||||
var targetType = target.GetType();
|
||||
return targetType.GetTypeStringValue(target, out source!);
|
||||
}
|
||||
else
|
||||
{
|
||||
source = null;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using System.Text;
|
||||
|
||||
namespace ThingsGateway.Foundation;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public class StringToEncodingConverter : ISerializerFormatter<string, object>
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public int Order { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool TryDeserialize(object state, in string source, Type targetType, out object target)
|
||||
{
|
||||
try
|
||||
{
|
||||
target = Encoding.Default;
|
||||
if (targetType == typeof(Encoding))
|
||||
{
|
||||
target = Encoding.GetEncoding(source);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
target = default;
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool TrySerialize(object state, in object target, out string source)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (target?.GetType() == typeof(Encoding))
|
||||
{
|
||||
source = (target as Encoding).WebName;
|
||||
return true;
|
||||
}
|
||||
source = target.ToJsonString();
|
||||
return true;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
source = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace ThingsGateway.Foundation;
|
||||
|
||||
/// <summary>
|
||||
/// String类型数据转换器
|
||||
/// </summary>
|
||||
public class ThingsGatewayStringConverter : StringSerializerConverter
|
||||
{
|
||||
/// <summary>
|
||||
/// 默认实例
|
||||
/// </summary>
|
||||
public static ThingsGatewayStringConverter Default = new ThingsGatewayStringConverter();
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数
|
||||
/// </summary>
|
||||
public ThingsGatewayStringConverter(params ISerializerFormatter<string, object>[] converters) : base(converters)
|
||||
{
|
||||
Add(new StringToClassConverter<object>());
|
||||
Add(new JsonStringToClassSerializerFormatter<object>());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace ThingsGateway.Foundation;
|
||||
|
||||
/// <summary>
|
||||
/// 采集返回消息
|
||||
/// </summary>
|
||||
public interface IResultMessage : IOperResult, IRequestInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// 数据体长度
|
||||
/// </summary>
|
||||
int BodyLength { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 解析的字节信息
|
||||
/// </summary>
|
||||
byte[] Content { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 消息头的指令长度,不固定时返回0
|
||||
/// </summary>
|
||||
int HeaderLength { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 等待标识,对于并发协议,必须从协议中例如固定头部获取标识字段
|
||||
/// </summary>
|
||||
int Sign { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 当收到数据,由框架封送有效载荷数据。
|
||||
/// 此时流位置为<see cref="HeaderLength"/>
|
||||
/// <para>但是如果是因为数据错误,则需要修改<see cref="ByteBlock.Position"/>到正确位置,如果都不正确,则设置<see cref="ByteBlock.Position"/>等于<see cref="ByteBlock.Length"/></para>
|
||||
/// <para>然后返回<see cref="FilterResult.GoOn"/></para>
|
||||
/// </summary>
|
||||
/// <returns>是否成功有效</returns>
|
||||
FilterResult CheckBody<TByteBlock>(ref TByteBlock byteBlock) where TByteBlock : IByteBlock;
|
||||
|
||||
/// <summary>
|
||||
/// 检查头子节的合法性,并赋值<see cref="BodyLength"/><br />
|
||||
/// <para>如果返回false,意味着放弃本次解析的所有数据,包括已经解析完成的Header</para>
|
||||
/// </summary>
|
||||
/// <returns>是否成功的结果</returns>
|
||||
bool CheckHead<TByteBlock>(ref TByteBlock byteBlock) where TByteBlock : IByteBlock;
|
||||
|
||||
/// <summary>
|
||||
/// 发送前的信息处理,例如存储某些特征信息:站号/功能码等等用于验证后续的返回信息是否合法
|
||||
/// </summary>
|
||||
/// <param name="sendMessage"></param>
|
||||
/// <returns></returns>
|
||||
void SendInfo(ISendMessage sendMessage);
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace ThingsGateway.Foundation;
|
||||
|
||||
/// <summary>
|
||||
/// 发送消息
|
||||
/// </summary>
|
||||
public interface ISendMessage : IRequestInfo, IWaitHandle, IRequestInfoBuilder
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace ThingsGateway.Foundation;
|
||||
|
||||
/// <inheritdoc cref="IResultMessage"/>
|
||||
public class MessageBase : OperResultClass<byte[]>, IResultMessage, IWaitHandle
|
||||
{
|
||||
#region 构造
|
||||
|
||||
/// <inheritdoc />
|
||||
public MessageBase() : base()
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public MessageBase(IOperResult operResult) : base(operResult)
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public MessageBase(Exception ex) : base(ex)
|
||||
{
|
||||
}
|
||||
|
||||
#endregion 构造
|
||||
|
||||
/// <inheritdoc/>
|
||||
public int BodyLength { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual int HeaderLength { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual int Sign { get; set; } = -1;
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual FilterResult CheckBody<TByteBlock>(ref TByteBlock byteBlock) where TByteBlock : IByteBlock
|
||||
{
|
||||
return FilterResult.Success;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual bool CheckHead<TByteBlock>(ref TByteBlock byteBlock) where TByteBlock : IByteBlock
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual void SendInfo(ISendMessage sendMessage)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,197 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using System.Text;
|
||||
|
||||
namespace ThingsGateway.Foundation;
|
||||
|
||||
/// <summary>
|
||||
/// TCP/Serial适配器基类
|
||||
/// </summary>
|
||||
public class ProtocolSingleStreamDataHandleAdapter<TRequest> : CustomDataHandlingAdapter<TRequest> where TRequest : MessageBase, new()
|
||||
{
|
||||
/// <inheritdoc cref="ProtocolSingleStreamDataHandleAdapter{TRequest}"/>
|
||||
public ProtocolSingleStreamDataHandleAdapter()
|
||||
{
|
||||
CacheTimeoutEnable = true;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool CanSendRequestInfo => true;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool CanSplicingSend => false;
|
||||
|
||||
/// <summary>
|
||||
/// 报文输出时采用字符串还是HexString
|
||||
/// </summary>
|
||||
public virtual bool IsHexData { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// 是否非并发协议
|
||||
/// </summary>
|
||||
public virtual bool IsSingleThread { get; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// 非并发协议中,每次交互的对象,会在发送时重新获取
|
||||
/// </summary>
|
||||
public TRequest Request { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public void SetRequest(int sign, ISendMessage sendMessage)
|
||||
{
|
||||
var request = GetInstance();
|
||||
request.Sign = sign;
|
||||
request.SendInfo(sendMessage);
|
||||
Request = request;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string? ToString()
|
||||
{
|
||||
return Owner?.ToString();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override FilterResult Filter<TByteBlock>(ref TByteBlock byteBlock, bool beCached, ref TRequest request, ref int tempCapacity)
|
||||
{
|
||||
if (Logger?.LogLevel <= LogLevel.Trace)
|
||||
Logger?.Trace($"{ToString()}- Receive:{(IsHexData ? byteBlock.AsSegmentTake().ToHexString() : byteBlock.ToString(byteBlock.Position))}");
|
||||
|
||||
try
|
||||
{
|
||||
{
|
||||
//非并发协议,复用对象
|
||||
if (IsSingleThread)
|
||||
request = Request == null ? GetInstance() : Request;
|
||||
else
|
||||
{
|
||||
if (!beCached)
|
||||
request = GetInstance();
|
||||
}
|
||||
|
||||
var pos = byteBlock.Position;
|
||||
|
||||
if (request.HeaderLength > byteBlock.CanReadLength)
|
||||
{
|
||||
return FilterResult.Cache;//当头部都无法解析时,直接缓存
|
||||
}
|
||||
|
||||
//检查头部合法性
|
||||
if (request.CheckHead(ref byteBlock))
|
||||
{
|
||||
byteBlock.Position = pos;
|
||||
if (request.BodyLength > MaxPackageSize)
|
||||
{
|
||||
OnError(default, $"Received BodyLength={request.BodyLength}, greater than the set MaxPackageSize={MaxPackageSize}", true, true);
|
||||
return FilterResult.GoOn;
|
||||
}
|
||||
if (request.BodyLength + request.HeaderLength > byteBlock.CanReadLength)
|
||||
{
|
||||
//body不满足解析,开始缓存,然后保存对象
|
||||
tempCapacity = request.BodyLength + request.HeaderLength;
|
||||
return FilterResult.Cache;
|
||||
}
|
||||
//if (request.BodyLength <= 0)
|
||||
//{
|
||||
// //如果body长度无法确定,直接读取全部
|
||||
// request.BodyLength = byteBlock.Length;
|
||||
//}
|
||||
var headPos = pos + request.HeaderLength;
|
||||
byteBlock.Position = headPos;
|
||||
var result = request.CheckBody(ref byteBlock);
|
||||
if (result == FilterResult.Cache)
|
||||
{
|
||||
if (Logger?.LogLevel <= LogLevel.Trace)
|
||||
Logger?.Trace($"{ToString()}-Received incomplete, cached message, current length:{byteBlock.Length} {request?.ErrorMessage}");
|
||||
tempCapacity = request.BodyLength + request.HeaderLength;
|
||||
request.OperCode = -1;
|
||||
}
|
||||
else if (result == FilterResult.GoOn)
|
||||
{
|
||||
byteBlock.Position = pos + 1;
|
||||
request.OperCode = -1;
|
||||
}
|
||||
else if (result == FilterResult.Success)
|
||||
{
|
||||
byteBlock.Position = request.HeaderLength + request.BodyLength + pos;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
else
|
||||
{
|
||||
byteBlock.Position = pos + 1;//移动游标
|
||||
request.OperCode = -1;
|
||||
return FilterResult.GoOn;//放弃解析
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger?.LogWarning(ex, $"{ToString()} Received parsing error");
|
||||
byteBlock.Position = byteBlock.Length;//移动游标
|
||||
return FilterResult.GoOn;//放弃解析
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取泛型实例。
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
protected virtual TRequest GetInstance()
|
||||
{
|
||||
return new TRequest() { OperCode = -1 };
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void OnReceivedSuccess(TRequest request)
|
||||
{
|
||||
Request = null;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override async Task PreviewSendAsync(ReadOnlyMemory<byte> memory)
|
||||
{
|
||||
if (Logger?.LogLevel <= LogLevel.Trace)
|
||||
Logger?.Trace($"{ToString()}- Send:{(IsHexData ? memory.Span.ToHexString() : (memory.Span.ToString(Encoding.UTF8)))}");
|
||||
|
||||
//发送
|
||||
await GoSendAsync(memory).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override async Task PreviewSendAsync(IRequestInfo requestInfo)
|
||||
{
|
||||
if (!(requestInfo is ISendMessage sendMessage))
|
||||
{
|
||||
throw new Exception($"Unable to convert {nameof(requestInfo)} to {nameof(ISendMessage)}");
|
||||
}
|
||||
|
||||
var requestInfoBuilder = (ISendMessage)requestInfo;
|
||||
|
||||
var byteBlock = new ValueByteBlock(requestInfoBuilder.MaxLength);
|
||||
try
|
||||
{
|
||||
requestInfoBuilder.Build(ref byteBlock);
|
||||
if (Logger?.LogLevel <= LogLevel.Trace)
|
||||
Logger?.Trace($"{ToString()}- Send:{(IsHexData ? byteBlock.Span.ToHexString() : (byteBlock.Span.ToString(Encoding.UTF8)))}");
|
||||
//非并发主从协议
|
||||
if (IsSingleThread)
|
||||
{
|
||||
SetRequest(sendMessage.Sign, requestInfoBuilder);
|
||||
}
|
||||
await GoSendAsync(byteBlock.Memory).ConfigureAwait(false);
|
||||
}
|
||||
finally
|
||||
{
|
||||
byteBlock.SafeDispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,183 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
|
||||
namespace ThingsGateway.Foundation;
|
||||
|
||||
/// <summary>
|
||||
/// UDP适配器基类
|
||||
/// </summary>
|
||||
public class ProtocolUdpDataHandleAdapter<TRequest> : UdpDataHandlingAdapter where TRequest : MessageBase, new()
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public override bool CanSendRequestInfo => true;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool CanSplicingSend => false;
|
||||
|
||||
/// <summary>
|
||||
/// 报文输出时采用字符串还是HexString
|
||||
/// </summary>
|
||||
public virtual bool IsHexData { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// 是否非并发协议
|
||||
/// </summary>
|
||||
public virtual bool IsSingleThread { get; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// 非并发协议中,每次交互的对象,会在发送时重新获取
|
||||
/// </summary>
|
||||
public TRequest Request { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public void SetRequest(int sign, ISendMessage sendMessage)
|
||||
{
|
||||
var request = GetInstance();
|
||||
request.Sign = sign;
|
||||
request.SendInfo(sendMessage);
|
||||
Request = request;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string? ToString()
|
||||
{
|
||||
return Owner.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取泛型实例。
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
protected virtual TRequest GetInstance()
|
||||
{
|
||||
return new TRequest() { OperCode = -1, Sign = -1 };
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override async Task PreviewReceived(EndPoint remoteEndPoint, ByteBlock byteBlock)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (Logger?.LogLevel <= LogLevel.Trace)
|
||||
Logger?.Trace($"{ToString()}- Receive:{(IsHexData ? byteBlock.AsSegmentTake().ToHexString() : byteBlock.ToString(byteBlock.Position))}");
|
||||
|
||||
TRequest request = null;
|
||||
if (IsSingleThread)
|
||||
request = Request == null ? GetInstance() : Request;
|
||||
else
|
||||
{
|
||||
request = GetInstance();
|
||||
}
|
||||
|
||||
var pos = byteBlock.Position;
|
||||
|
||||
if (request.HeaderLength > byteBlock.CanReadLength)
|
||||
{
|
||||
return;//当头部都无法解析时,直接缓存
|
||||
}
|
||||
|
||||
//检查头部合法性
|
||||
if (request.CheckHead(ref byteBlock))
|
||||
{
|
||||
byteBlock.Position = pos;
|
||||
|
||||
if (request.BodyLength > MaxPackageSize)
|
||||
{
|
||||
OnError(default, $"Received BodyLength={request.BodyLength}, greater than the set MaxPackageSize={MaxPackageSize}", true, true);
|
||||
return;
|
||||
}
|
||||
if (request.BodyLength + request.HeaderLength > byteBlock.CanReadLength)
|
||||
{
|
||||
//body不满足解析,开始缓存,然后保存对象
|
||||
return;
|
||||
}
|
||||
//if (request.BodyLength <= 0)
|
||||
//{
|
||||
// //如果body长度无法确定,直接读取全部
|
||||
// request.BodyLength = byteBlock.Length;
|
||||
//}
|
||||
var headPos = pos + request.HeaderLength;
|
||||
byteBlock.Position = headPos;
|
||||
var result = request.CheckBody(ref byteBlock);
|
||||
if (result == FilterResult.Cache)
|
||||
{
|
||||
if (Logger?.LogLevel <= LogLevel.Trace)
|
||||
Logger?.Trace($"{ToString()}-Received incomplete, cached message, current length:{byteBlock.Length} {request?.ErrorMessage}");
|
||||
request.OperCode = -1;
|
||||
}
|
||||
else if (result == FilterResult.GoOn)
|
||||
{
|
||||
byteBlock.Position = pos + 1;
|
||||
request.OperCode = -1;
|
||||
}
|
||||
else if (result == FilterResult.Success)
|
||||
{
|
||||
byteBlock.Position = request.HeaderLength + request.BodyLength + pos;
|
||||
await GoReceived(remoteEndPoint, null, request).ConfigureAwait(false);
|
||||
}
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
byteBlock.Position = pos + 1;
|
||||
request.OperCode = -1;
|
||||
return;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger?.LogWarning(ex, $"{ToString()} Received parsing error");
|
||||
byteBlock.Position = byteBlock.Length;//移动游标
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override async Task PreviewSendAsync(EndPoint endPoint, ReadOnlyMemory<byte> memory)
|
||||
{
|
||||
if (Logger?.LogLevel <= LogLevel.Trace)
|
||||
Logger?.Trace($"{ToString()}- Send:{(IsHexData ? memory.Span.ToHexString() : (memory.Span.ToString(Encoding.UTF8)))}");
|
||||
|
||||
//发送
|
||||
await GoSendAsync(endPoint, memory).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override async Task PreviewSendAsync(EndPoint endPoint, IRequestInfo requestInfo)
|
||||
{
|
||||
if (!(requestInfo is ISendMessage sendMessage))
|
||||
{
|
||||
throw new Exception($"Unable to convert {nameof(requestInfo)} to {nameof(ISendMessage)}");
|
||||
}
|
||||
|
||||
var requestInfoBuilder = (ISendMessage)requestInfo;
|
||||
|
||||
var byteBlock = new ValueByteBlock(requestInfoBuilder.MaxLength);
|
||||
try
|
||||
{
|
||||
requestInfoBuilder.Build(ref byteBlock);
|
||||
if (Logger?.LogLevel <= LogLevel.Trace)
|
||||
Logger?.Trace($"{ToString()}- Send:{(IsHexData ? byteBlock.Span.ToHexString() : (byteBlock.Span.ToString(Encoding.UTF8)))}");
|
||||
//非并发主从协议
|
||||
if (IsSingleThread)
|
||||
{
|
||||
SetRequest(sendMessage.Sign, requestInfoBuilder);
|
||||
}
|
||||
await GoSendAsync(endPoint, byteBlock.Memory).ConfigureAwait(false);
|
||||
}
|
||||
finally
|
||||
{
|
||||
byteBlock.SafeDispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace ThingsGateway.Foundation;
|
||||
|
||||
/// <summary>
|
||||
/// Bcd格式化值
|
||||
/// </summary>
|
||||
public enum BcdFormatEnum
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
C8421 = 1,
|
||||
|
||||
/// <inheritdoc/>
|
||||
C5421 = 2,
|
||||
|
||||
/// <inheritdoc/>
|
||||
C2421 = 3,
|
||||
|
||||
/// <inheritdoc/>
|
||||
C3 = 4,
|
||||
|
||||
/// <inheritdoc/>
|
||||
Gray = 5,
|
||||
}
|
||||
42
src/Foundation/ThingsGateway.Foundation/Enums/ChannelEnum.cs
Normal file
42
src/Foundation/ThingsGateway.Foundation/Enums/ChannelEnum.cs
Normal file
@@ -0,0 +1,42 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace ThingsGateway.Foundation;
|
||||
|
||||
/// <summary>
|
||||
/// 通道类型
|
||||
/// </summary>
|
||||
public enum ChannelTypeEnum
|
||||
{
|
||||
/// <summary>
|
||||
/// Tcp客户端
|
||||
/// </summary>
|
||||
TcpClient,
|
||||
|
||||
/// <summary>
|
||||
/// Tcp服务器
|
||||
/// </summary>
|
||||
TcpService,
|
||||
|
||||
/// <summary>
|
||||
/// 串口
|
||||
/// </summary>
|
||||
SerialPort,
|
||||
|
||||
/// <summary>
|
||||
/// Udp
|
||||
/// </summary>
|
||||
UdpSession,
|
||||
|
||||
/// <summary>
|
||||
/// Other
|
||||
/// </summary>
|
||||
Other,
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
// ------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
// ------------------------------------------------------------------------------
|
||||
|
||||
namespace ThingsGateway.Foundation;
|
||||
|
||||
/// <summary>
|
||||
/// 应用于多字节数据的解析或是生成格式
|
||||
/// </summary>
|
||||
public enum DataFormatEnum
|
||||
{
|
||||
/// <summary>Big-Endian</summary>
|
||||
ABCD,
|
||||
|
||||
/// <summary>Big-Endian Byte Swap</summary>
|
||||
BADC,
|
||||
|
||||
/// <summary>Little-Endian Byte Swap</summary>
|
||||
CDAB,
|
||||
|
||||
/// <summary>Little-Endian</summary>
|
||||
DCBA,
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace ThingsGateway.Foundation;
|
||||
|
||||
/// <summary>
|
||||
/// 数据类型
|
||||
/// </summary>
|
||||
public enum DataTypeEnum
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
Object,
|
||||
|
||||
/// <inheritdoc/>
|
||||
String,
|
||||
|
||||
/// <inheritdoc/>
|
||||
Boolean,
|
||||
|
||||
/// <inheritdoc/>
|
||||
Byte,
|
||||
|
||||
/// <inheritdoc/>
|
||||
Int16,
|
||||
|
||||
/// <inheritdoc/>
|
||||
UInt16,
|
||||
|
||||
/// <inheritdoc/>
|
||||
Int32,
|
||||
|
||||
/// <inheritdoc/>
|
||||
UInt32,
|
||||
|
||||
/// <inheritdoc/>
|
||||
Int64,
|
||||
|
||||
/// <inheritdoc/>
|
||||
UInt64,
|
||||
|
||||
/// <inheritdoc/>
|
||||
Single,
|
||||
|
||||
/// <inheritdoc/>
|
||||
Double,
|
||||
|
||||
/// <inheritdoc/>
|
||||
Decimal,
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace ThingsGateway.Foundation;
|
||||
|
||||
/// <summary>
|
||||
/// bool扩展
|
||||
/// </summary>
|
||||
public static class BoolExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// 将bool数组转换到byte数组
|
||||
/// </summary>
|
||||
public static byte[] BoolArrayToByte(this bool[] array)
|
||||
{
|
||||
int byteLength = (array.Length + 7) / 8;
|
||||
byte[] byteArray = new byte[byteLength];
|
||||
|
||||
for (int i = 0; i < array.Length; i++)
|
||||
{
|
||||
if (array[i])
|
||||
{
|
||||
int byteIndex = i / 8;
|
||||
int bitOffset = i % 8;
|
||||
byteArray[byteIndex] |= (byte)(1 << bitOffset);
|
||||
}
|
||||
}
|
||||
|
||||
return byteArray;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,232 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using System.Text;
|
||||
|
||||
using ThingsGateway.Foundation.Extension.Generic;
|
||||
|
||||
namespace ThingsGateway.Foundation;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public static class ByteExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取byte数据类型的第offset位,是否为True<br />
|
||||
/// </summary>
|
||||
/// <param name="value">byte数值</param>
|
||||
/// <param name="offset">索引位置</param>
|
||||
/// <returns>结果</returns>
|
||||
public static bool BoolOnByteIndex(this byte value, int offset)
|
||||
{
|
||||
if (offset < 0 || offset > 7)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(offset), "Offset value must be between 0 and 7.");
|
||||
}
|
||||
|
||||
byte mask = (byte)(1 << offset);
|
||||
return (value & mask) == mask;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 数组内容分别相加某个数字
|
||||
/// </summary>
|
||||
/// <param name="bytes"></param>
|
||||
/// <param name="value"></param>
|
||||
/// <returns></returns>
|
||||
public static byte[] BytesAdd(this byte[] bytes, int value)
|
||||
{
|
||||
if (bytes == null || bytes.Length == 0)
|
||||
{
|
||||
throw new ArgumentException("Input byte array is null or empty");
|
||||
}
|
||||
|
||||
byte[] result = new byte[bytes.Length];
|
||||
for (int index = 0; index < bytes.Length; index++)
|
||||
{
|
||||
result[index] = (byte)(bytes[index] + value);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 数组内容分别相加某个数字
|
||||
/// </summary>
|
||||
/// <param name="bytes"></param>
|
||||
/// <param name="value"></param>
|
||||
/// <returns></returns>
|
||||
public static ReadOnlySpan<byte> BytesAdd(this ReadOnlySpan<byte> bytes, int value)
|
||||
{
|
||||
byte[] result = new byte[bytes.Length];
|
||||
for (int index = 0; index < bytes.Length; index++)
|
||||
{
|
||||
result[index] = (byte)(bytes[index] + value);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将byte数组按照双字节进行反转,如果为单数的情况,则自动补齐<br />
|
||||
/// </summary>
|
||||
/// <param name="inBytes">输入的字节信息</param>
|
||||
/// <returns>反转后的数据</returns>
|
||||
public static byte[] BytesReverseByWord(this byte[] inBytes)
|
||||
{
|
||||
if (inBytes.Length == 0)
|
||||
{
|
||||
return Array.Empty<byte>();
|
||||
}
|
||||
// 创建新数组进行补齐
|
||||
byte[] paddedBytes = inBytes.CopyArray<byte>().ArrayExpandToLengthEven();
|
||||
// 进行双字节反转
|
||||
for (int index = 0; index < paddedBytes.Length; index += 2)
|
||||
{
|
||||
byte temp = paddedBytes[index];
|
||||
paddedBytes[index] = paddedBytes[index + 1];
|
||||
paddedBytes[index + 1] = temp;
|
||||
}
|
||||
|
||||
return paddedBytes;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从字节数组中提取位数组,length 代表位数
|
||||
/// </summary>
|
||||
/// <param name="inBytes">原始的字节数组</param>
|
||||
/// <param name="length">想要转换的位数,如果超出字节数组长度 * 8,则自动缩小为数组最大长度</param>
|
||||
/// <returns>转换后的布尔数组</returns>
|
||||
public static bool[] ByteToBoolArray(this byte[] inBytes, int length)
|
||||
{
|
||||
// 计算字节数组能够提供的最大位数
|
||||
int maxBitLength = inBytes.Length * 8;
|
||||
|
||||
// 如果指定长度超出最大位数,则将长度缩小为最大位数
|
||||
if (length > maxBitLength)
|
||||
{
|
||||
length = maxBitLength;
|
||||
}
|
||||
|
||||
// 创建对应长度的布尔数组
|
||||
bool[] boolArray = new bool[length];
|
||||
|
||||
// 从字节数组中提取位信息并转换为布尔值存储到布尔数组中
|
||||
for (int index = 0; index < length; ++index)
|
||||
{
|
||||
boolArray[index] = inBytes[index / 8].BoolOnByteIndex(index % 8);
|
||||
}
|
||||
|
||||
return boolArray;
|
||||
}
|
||||
/// <summary>
|
||||
/// 从字节数组中提取位数组,length 代表位数
|
||||
/// </summary>
|
||||
/// <param name="inBytes">原始的字节数组</param>
|
||||
/// <param name="length">想要转换的位数,如果超出字节数组长度 * 8,则自动缩小为数组最大长度</param>
|
||||
/// <returns>转换后的布尔数组</returns>
|
||||
public static bool[] ByteToBoolArray(this Span<byte> inBytes, int length)
|
||||
{
|
||||
// 计算字节数组能够提供的最大位数
|
||||
int maxBitLength = inBytes.Length * 8;
|
||||
|
||||
// 如果指定长度超出最大位数,则将长度缩小为最大位数
|
||||
if (length > maxBitLength)
|
||||
{
|
||||
length = maxBitLength;
|
||||
}
|
||||
|
||||
// 创建对应长度的布尔数组
|
||||
bool[] boolArray = new bool[length];
|
||||
|
||||
// 从字节数组中提取位信息并转换为布尔值存储到布尔数组中
|
||||
for (int index = 0; index < length; ++index)
|
||||
{
|
||||
boolArray[index] = inBytes[index / 8].BoolOnByteIndex(index % 8);
|
||||
}
|
||||
|
||||
return boolArray;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取异或校验
|
||||
/// </summary>
|
||||
/// <param name="data"></param>
|
||||
/// <param name="left"></param>
|
||||
/// <param name="right"></param>
|
||||
/// <returns></returns>
|
||||
public static byte[] GetAsciiXOR(this byte[] data, int left, int right)
|
||||
{
|
||||
if (data == null || left < 0 || right < 0 || left >= data.Length || right >= data.Length || left > right)
|
||||
{
|
||||
throw new ArgumentException("Invalid input parameters");
|
||||
}
|
||||
|
||||
byte tmp = data[left];
|
||||
for (int i = left + 1; i <= right; i++)
|
||||
{
|
||||
tmp ^= data[i];
|
||||
}
|
||||
|
||||
return Encoding.ASCII.GetBytes(tmp.ToString("X2"));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取Byte数组的第 boolIndex 偏移的bool值,这个偏移值可以为 10,就是第 1 个字节的 第3位 <br />
|
||||
/// </summary>
|
||||
/// <param name="bytes">字节数组信息</param>
|
||||
/// <param name="boolIndex">指定字节的位偏移</param>
|
||||
/// <returns>bool值</returns>
|
||||
public static bool GetBoolByIndex(this byte[] bytes, int boolIndex)
|
||||
{
|
||||
return bytes[boolIndex / 8].BoolOnByteIndex(boolIndex % 8);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 字节数组默认转16进制字符
|
||||
/// </summary>
|
||||
/// <param name="buffer"></param>
|
||||
/// <param name="splite"></param>
|
||||
/// <returns></returns>
|
||||
public static string ToHexString(this ArraySegment<byte> buffer, char splite = ' ')
|
||||
{
|
||||
return DataTransUtil.ByteToHexString(buffer, splite);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 字节数组默认转16进制字符
|
||||
/// </summary>
|
||||
/// <param name="buffer"></param>
|
||||
/// <param name="splite"></param>
|
||||
/// <returns></returns>
|
||||
public static string ToHexString(this ReadOnlySpan<byte> buffer, char splite = ' ')
|
||||
{
|
||||
return DataTransUtil.ByteToHexString(buffer, splite);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 字节数组默认转16进制字符
|
||||
/// </summary>
|
||||
/// <param name="buffer"></param>
|
||||
/// <param name="splite"></param>
|
||||
/// <returns></returns>
|
||||
public static string ToHexString(this byte[] buffer, char splite = default)
|
||||
{
|
||||
return DataTransUtil.ByteToHexString(buffer, splite);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 字节数组默认转16进制字符
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static string ToHexString(this byte[] buffer, int offset, int length, char splite = ' ', int newLineCount = 0)
|
||||
{
|
||||
return DataTransUtil.ByteToHexString(buffer, offset, length, splite, newLineCount);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using System.Collections.Concurrent;
|
||||
|
||||
namespace ThingsGateway.Foundation.Extension.Collection;
|
||||
|
||||
/// <summary>
|
||||
/// 对象拓展
|
||||
/// </summary>
|
||||
public static class CollectionExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// 添加多个元素
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="this"></param>
|
||||
/// <param name="values"></param>
|
||||
public static void AddRange<T>(this ICollection<T> @this, IEnumerable<T> values)
|
||||
{
|
||||
foreach (var obj in values)
|
||||
{
|
||||
@this.Add(obj);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从并发字典中删除
|
||||
/// </summary>
|
||||
public static bool Remove<TKey, TValue>(this ConcurrentDictionary<TKey, TValue> dict, TKey key) where TKey : notnull
|
||||
{
|
||||
return dict.TryRemove(key, out TValue? _);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 移除符合条件的元素
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="this"></param>
|
||||
/// <param name="where"></param>
|
||||
public static void RemoveWhere<T>(this ICollection<T> @this, Func<T, bool> @where)
|
||||
{
|
||||
foreach (var obj in @this.Where(where).ToList())
|
||||
{
|
||||
@this.Remove(obj);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace ThingsGateway.Foundation;
|
||||
|
||||
/// <summary>
|
||||
/// 数据类型信息
|
||||
/// </summary>
|
||||
public static class DataTypeExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取实际字节长度,不能确定返回0,bool返回1
|
||||
/// </summary>
|
||||
/// <param name="coreDataType"></param>
|
||||
/// <returns></returns>
|
||||
public static byte GetByteLength(this DataTypeEnum coreDataType)
|
||||
{
|
||||
return coreDataType switch
|
||||
{
|
||||
DataTypeEnum.Boolean => 1,
|
||||
DataTypeEnum.Byte => 1,
|
||||
DataTypeEnum.Int16 => 2,
|
||||
DataTypeEnum.UInt16 => 2,
|
||||
DataTypeEnum.Int32 => 4,
|
||||
DataTypeEnum.UInt32 => 4,
|
||||
DataTypeEnum.Int64 => 8,
|
||||
DataTypeEnum.UInt64 => 8,
|
||||
DataTypeEnum.Single => 4,
|
||||
DataTypeEnum.Double => 8,
|
||||
DataTypeEnum.Decimal => 16,
|
||||
_ => 0,
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取DataTypeEnum
|
||||
/// </summary>
|
||||
/// <param name="coreType"></param>
|
||||
/// <returns></returns>
|
||||
public static DataTypeEnum GetDataType(this TypeCode coreType)
|
||||
{
|
||||
return coreType switch
|
||||
{
|
||||
TypeCode.String => DataTypeEnum.String,
|
||||
TypeCode.Boolean => DataTypeEnum.Boolean,
|
||||
TypeCode.Byte => DataTypeEnum.Byte,
|
||||
TypeCode.Int16 => DataTypeEnum.Int16,
|
||||
TypeCode.UInt16 => DataTypeEnum.UInt16,
|
||||
TypeCode.Int32 => DataTypeEnum.Int32,
|
||||
TypeCode.UInt32 => DataTypeEnum.UInt32,
|
||||
TypeCode.Int64 => DataTypeEnum.Int64,
|
||||
TypeCode.UInt64 => DataTypeEnum.UInt64,
|
||||
TypeCode.Single => DataTypeEnum.Single,
|
||||
TypeCode.Double => DataTypeEnum.Double,
|
||||
_ => DataTypeEnum.Object,
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取DOTNET RUNTIME TYPE
|
||||
/// </summary>
|
||||
/// <param name="coreDataType"></param>
|
||||
/// <returns></returns>
|
||||
public static Type GetSystemType(this DataTypeEnum coreDataType)
|
||||
{
|
||||
return coreDataType switch
|
||||
{
|
||||
DataTypeEnum.String => typeof(string),
|
||||
DataTypeEnum.Boolean => typeof(bool),
|
||||
DataTypeEnum.Byte => typeof(byte),
|
||||
DataTypeEnum.Int16 => typeof(short),
|
||||
DataTypeEnum.UInt16 => typeof(ushort),
|
||||
DataTypeEnum.Int32 => typeof(int),
|
||||
DataTypeEnum.UInt32 => typeof(uint),
|
||||
DataTypeEnum.Int64 => typeof(long),
|
||||
DataTypeEnum.UInt64 => typeof(ulong),
|
||||
DataTypeEnum.Single => typeof(float),
|
||||
DataTypeEnum.Double => typeof(double),
|
||||
_ => typeof(object),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
// ------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
// ------------------------------------------------------------------------------
|
||||
namespace ThingsGateway.Foundation;
|
||||
|
||||
/// <summary>
|
||||
/// 时间扩展类
|
||||
/// </summary>
|
||||
public static class DateTimeExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// DateTime转Unix时间戳
|
||||
/// </summary>
|
||||
/// <param name="dateTime"></param>
|
||||
/// <returns></returns>
|
||||
public static long DateTimeToUnixTimestamp(this DateTime dateTime)
|
||||
{
|
||||
// Unix 时间起点
|
||||
var unixStart = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
|
||||
// 计算时间差
|
||||
var timeSpan = dateTime.ToUniversalTime() - unixStart;
|
||||
// 返回毫秒数
|
||||
return (long)timeSpan.TotalMilliseconds;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace System;
|
||||
|
||||
/// <summary>
|
||||
/// 异常扩展
|
||||
/// </summary>
|
||||
public static class ExceptionExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// null检查
|
||||
/// </summary>
|
||||
/// <param name="argument"></param>
|
||||
/// <param name="paramName"></param>
|
||||
/// <exception cref="ArgumentNullException"></exception>
|
||||
public static void ThrowIfNull([NotNull] this object? argument, string paramName)
|
||||
{
|
||||
if (argument == null)
|
||||
{
|
||||
throw new ArgumentNullException(paramName);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using System.Dynamic; // 引入 System.Dynamic 命名空间
|
||||
using System.Reflection;
|
||||
|
||||
namespace ThingsGateway.Foundation.Extension.Dynamic;
|
||||
|
||||
/// <summary>
|
||||
/// 提供对动态类型的扩展方法
|
||||
/// </summary>
|
||||
public static class ExpandoObjectExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// 将动态对象转换为指定类型实体
|
||||
/// </summary>
|
||||
/// <param name="expandoObject">动态对象</param>
|
||||
/// <param name="type">要转换的目标实体类型</param>
|
||||
/// <param name="properties"></param>
|
||||
/// <returns>转换后的实体对象</returns>
|
||||
public static object ConvertToEntity(this ExpandoObject expandoObject, Type type, Dictionary<string, PropertyInfo> properties)
|
||||
{
|
||||
var entity = Activator.CreateInstance(type);
|
||||
|
||||
// 遍历动态对象的属性
|
||||
expandoObject.ForEach(keyValuePair =>
|
||||
{
|
||||
// 检查动态对象的属性是否存在于目标类型的属性中
|
||||
if (properties.TryGetValue(keyValuePair.Key, out var property))
|
||||
{
|
||||
var value = keyValuePair.Value; // 获取动态属性的值
|
||||
// 将动态属性值转换为目标属性类型并设置到目标对象的属性中
|
||||
property.SetValue(entity, ThingsGatewayStringConverter.Default.Deserialize(null, value?.ToString(), property.PropertyType));
|
||||
}
|
||||
});
|
||||
return entity; // 返回转换后的实体对象
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将动态对象转换为指定类型实体
|
||||
/// </summary>
|
||||
/// <typeparam name="T">要转换的目标实体类型</typeparam>
|
||||
/// <param name="expandoObject">动态对象</param>
|
||||
/// <param name="properties"></param>
|
||||
/// <returns>转换后的实体对象</returns>
|
||||
public static T ConvertToEntity<T>(this ExpandoObject expandoObject, Dictionary<string, PropertyInfo> properties) where T : new()
|
||||
{
|
||||
var entity = new T(); // 创建目标类型的实例
|
||||
|
||||
// 遍历动态对象的属性
|
||||
expandoObject.ForEach(keyValuePair =>
|
||||
{
|
||||
// 检查动态对象的属性是否存在于目标类型的属性中
|
||||
if (properties.TryGetValue(keyValuePair.Key, out var property))
|
||||
{
|
||||
var value = keyValuePair.Value; // 获取动态属性的值
|
||||
// 将动态属性值转换为目标属性类型并设置到目标对象的属性中
|
||||
property.SetValue(entity, ThingsGatewayStringConverter.Default.Deserialize(null, value?.ToString(), property.PropertyType));
|
||||
}
|
||||
});
|
||||
return entity; // 返回转换后的实体对象
|
||||
}
|
||||
|
||||
///// <summary>
|
||||
///// 获取动态对象中指定属性的值
|
||||
///// </summary>
|
||||
///// <typeparam name="T">动态对象的类型</typeparam>
|
||||
///// <param name="expandoObject">动态对象</param>
|
||||
///// <param name="propertyName">要获取值的属性名称</param>
|
||||
///// <returns>属性的值</returns>
|
||||
//public static object GetProperty<T>(this ExpandoObject expandoObject, string propertyName)
|
||||
//{
|
||||
// var type = typeof(T); // 获取动态对象的类型
|
||||
// var properties = type.GetRuntimeProperties(); // 获取动态对象的所有属性
|
||||
// var propertyDes = type.GetPropertyDisplayName(propertyName); // 获取指定属性的描述名称
|
||||
// return expandoObject.FirstOrDefault(a => a.Key == propertyDes).Value; // 返回指定属性的值
|
||||
//}
|
||||
}
|
||||
@@ -0,0 +1,244 @@
|
||||
// ------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
// ------------------------------------------------------------------------------
|
||||
|
||||
namespace ThingsGateway.Foundation.Extension.Generic;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public static class GenericExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// 将一个数组进行扩充到指定长度,或是缩短到指定长度<br />
|
||||
/// </summary>
|
||||
public static T[] ArrayExpandToLength<T>(this T[] data, int length)
|
||||
{
|
||||
if (data == null)
|
||||
{
|
||||
return new T[length];
|
||||
}
|
||||
|
||||
if (data.Length == length)
|
||||
{
|
||||
return data;
|
||||
}
|
||||
Array.Resize(ref data, length);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将一个数组进行扩充到偶数长度<br />
|
||||
/// </summary>
|
||||
public static T[] ArrayExpandToLengthEven<T>(this T[] data)
|
||||
{
|
||||
if (data == null)
|
||||
{
|
||||
return Array.Empty<T>();
|
||||
}
|
||||
|
||||
return data.Length % 2 == 1 ? data.ArrayExpandToLength(data.Length + 1) : data;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc cref="ArrayRemoveDouble{T}(T[], int, int)"/>
|
||||
/// </summary>
|
||||
public static T[] ArrayRemoveBegin<T>(T[] value, int length) => ArrayRemoveDouble(value, length, 0);
|
||||
|
||||
/// <summary>
|
||||
/// 从数组中移除指定数量的元素,并返回新的数组
|
||||
/// </summary>
|
||||
/// <typeparam name="T">数组元素类型</typeparam>
|
||||
/// <param name="value">要移除元素的数组</param>
|
||||
/// <param name="leftLength">从左侧移除的元素个数</param>
|
||||
/// <param name="rightLength">从右侧移除的元素个数</param>
|
||||
/// <returns>移除元素后的新数组</returns>
|
||||
public static T[] ArrayRemoveDouble<T>(T[] value, int leftLength, int rightLength)
|
||||
{
|
||||
// 如果输入数组为空或者剩余长度不足以移除左右两侧指定的元素,则返回空数组
|
||||
if (value == null || value.Length <= leftLength + rightLength)
|
||||
{
|
||||
return Array.Empty<T>();
|
||||
}
|
||||
|
||||
// 计算新数组的长度
|
||||
int newLength = value.Length - leftLength - rightLength;
|
||||
|
||||
// 创建新数组
|
||||
T[] result = new T[newLength];
|
||||
|
||||
// 将剩余的元素复制到新数组中
|
||||
Array.Copy(value, leftLength, result, 0, newLength);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将指定的数据按照指定长度进行分割
|
||||
/// </summary>
|
||||
public static List<T[]> ArraySplitByLength<T>(this T[] array, int length)
|
||||
{
|
||||
if (array == null || array.Length == 0)
|
||||
{
|
||||
return new List<T[]>();
|
||||
}
|
||||
|
||||
int arrayLength = array.Length;
|
||||
int numArrays = (arrayLength + length - 1) / length; // 计算所需的数组数量
|
||||
|
||||
List<T[]> objArrayList = new List<T[]>(numArrays);
|
||||
for (int i = 0; i < arrayLength; i += length)
|
||||
{
|
||||
int remainingLength = Math.Min(arrayLength - i, length);
|
||||
T[] destinationArray = new T[remainingLength];
|
||||
Array.Copy(array, i, destinationArray, 0, remainingLength);
|
||||
objArrayList.Add(destinationArray);
|
||||
}
|
||||
|
||||
return objArrayList;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将项目列表分解为特定大小的块
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="source">原数组</param>
|
||||
/// <param name="chunkSize">分组大小</param>
|
||||
/// <param name="isToList">是否ToList</param>
|
||||
/// <returns></returns>
|
||||
public static IEnumerable<IEnumerable<T>> ChunkBetter<T>(this IEnumerable<T> source, int chunkSize, bool isToList = false)
|
||||
{
|
||||
if (chunkSize <= 0)
|
||||
chunkSize = source.Count();
|
||||
var pos = 0;
|
||||
while (source.Skip(pos).Any())
|
||||
{
|
||||
var chunk = source.Skip(pos).Take(chunkSize);
|
||||
yield return isToList ? chunk.ToList() : chunk;
|
||||
pos += chunkSize;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>拷贝当前的实例数组,是基于引用层的浅拷贝,如果类型为值类型,那就是深度拷贝,如果类型为引用类型,就是浅拷贝</summary>
|
||||
public static T[] CopyArray<T>(this T[] value)
|
||||
{
|
||||
if (value == null)
|
||||
{
|
||||
return Array.Empty<T>();
|
||||
}
|
||||
|
||||
T[] destinationArray = new T[value.Length];
|
||||
Array.Copy(value, destinationArray, value.Length);
|
||||
return destinationArray;
|
||||
}
|
||||
|
||||
/// <summary>将一个一维数组中的所有数据按照行列信息拷贝到二维数组里,返回当前的二维数组</summary>
|
||||
public static T[,] CreateTwoArrayFromOneArray<T>(this T[] array, int row, int col)
|
||||
{
|
||||
T[,] arrayFromOneArray = new T[row, col];
|
||||
int index = 0;
|
||||
|
||||
for (int i = 0; i < row; i++)
|
||||
{
|
||||
for (int j = 0; j < col; j++)
|
||||
{
|
||||
arrayFromOneArray[i, j] = array[index++];
|
||||
if (index >= array.Length) return arrayFromOneArray; // 防止数组越界
|
||||
}
|
||||
}
|
||||
|
||||
return arrayFromOneArray;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将一个数组的前后移除指定位数,返回新的一个数组<br />
|
||||
/// </summary>
|
||||
public static T[] RemoveArray<T>(this T[] value, int leftLength, int rightLength)
|
||||
{
|
||||
if (value == null || value.Length == 0)
|
||||
{
|
||||
return Array.Empty<T>();
|
||||
}
|
||||
|
||||
int newLength = value.Length - leftLength - rightLength;
|
||||
if (newLength <= 0)
|
||||
{
|
||||
return Array.Empty<T>();
|
||||
}
|
||||
|
||||
T[] result = new T[newLength];
|
||||
Array.Copy(value, leftLength, result, 0, newLength);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将一个数组的前面指定位数移除,返回新的一个数组<br />
|
||||
/// </summary>
|
||||
public static T[] RemoveBegin<T>(this T[] value, int length) => value.RemoveArray(length, 0);
|
||||
|
||||
/// <summary>
|
||||
/// 将一个数组的后面指定位数移除,返回新的一个数组<br />
|
||||
/// </summary>
|
||||
public static T[] RemoveLast<T>(this T[] value, int length) => value.RemoveArray(0, length);
|
||||
|
||||
/// <summary>
|
||||
/// 选择数组中的最后几个元素组成新的数组
|
||||
/// </summary>
|
||||
/// <typeparam name="T">数组元素类型</typeparam>
|
||||
/// <param name="value">输入数组</param>
|
||||
/// <param name="length">选择的元素个数</param>
|
||||
/// <returns>由最后几个元素组成的新数组</returns>
|
||||
public static T[] SelectLast<T>(this T[] value, int length)
|
||||
{
|
||||
// 如果输入数组为空,则返回空数组
|
||||
if (value == null || value.Length == 0)
|
||||
{
|
||||
return Array.Empty<T>();
|
||||
}
|
||||
|
||||
// 计算实际需要复制的元素个数,取输入数组长度和指定长度的较小值
|
||||
int count = Math.Min(value.Length, length);
|
||||
|
||||
// 创建新数组来存储选择的元素
|
||||
T[] result = new T[count];
|
||||
|
||||
// 复制最后几个元素到新数组中
|
||||
Array.Copy(value, value.Length - count, result, 0, count);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从数组中获取指定索引开始的中间一段长度的子数组
|
||||
/// </summary>
|
||||
/// <typeparam name="T">数组元素类型</typeparam>
|
||||
/// <param name="value">输入数组</param>
|
||||
/// <param name="index">起始索引</param>
|
||||
/// <param name="length">选择的元素个数</param>
|
||||
/// <returns>中间指定长度的子数组</returns>
|
||||
public static T[] SelectMiddle<T>(this T[] value, int index, int length)
|
||||
{
|
||||
// 如果输入数组为空,则返回空数组
|
||||
if (value == null || value.Length == 0)
|
||||
{
|
||||
return Array.Empty<T>();
|
||||
}
|
||||
|
||||
// 计算实际需要复制的元素个数,取输入数组剩余元素和指定长度的较小值
|
||||
int count = Math.Min(value.Length - index, length);
|
||||
|
||||
// 创建新数组来存储选择的元素
|
||||
T[] result = new T[count];
|
||||
|
||||
// 复制中间指定长度的元素到新数组中
|
||||
Array.Copy(value, index, result, 0, count);
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace ThingsGateway.Foundation.Json.Extension;
|
||||
|
||||
/// <summary>
|
||||
/// json扩展
|
||||
/// </summary>
|
||||
public static class JsonExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// 默认Json规则
|
||||
/// </summary>
|
||||
public static JsonSerializerSettings Options = new JsonSerializerSettings
|
||||
{
|
||||
Formatting = Formatting.Indented,// 使用缩进格式化输出
|
||||
NullValueHandling = NullValueHandling.Ignore // 忽略空值属性
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// 反序列化
|
||||
/// </summary>
|
||||
/// <param name="json"></param>
|
||||
/// <param name="type"></param>
|
||||
/// <param name="jsonSerializerSettings"></param>
|
||||
/// <returns></returns>
|
||||
public static object FromJsonNetString(this string json, Type type, JsonSerializerSettings? jsonSerializerSettings = null)
|
||||
{
|
||||
return Newtonsoft.Json.JsonConvert.DeserializeObject(json, type, jsonSerializerSettings ?? Options);
|
||||
}
|
||||
/// <summary>
|
||||
/// 反序列化
|
||||
/// </summary>
|
||||
/// <param name="json"></param>
|
||||
/// <param name="jsonSerializerSettings"></param>
|
||||
/// <returns></returns>
|
||||
public static T FromJsonNetString<T>(this string json, JsonSerializerSettings? jsonSerializerSettings = null)
|
||||
{
|
||||
return (T)FromJsonNetString(json, typeof(T), jsonSerializerSettings);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 序列化
|
||||
/// </summary>
|
||||
/// <param name="item"></param>
|
||||
/// <param name="jsonSerializerSettings"></param>
|
||||
/// <returns></returns>
|
||||
public static string ToJsonNetString(this object item, JsonSerializerSettings? jsonSerializerSettings = null)
|
||||
{
|
||||
return Newtonsoft.Json.JsonConvert.SerializeObject(item, jsonSerializerSettings ?? Options);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
using ThingsGateway.NewLife.Extension;
|
||||
|
||||
namespace ThingsGateway.Foundation;
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc/>
|
||||
/// </summary>
|
||||
public static class LoggerExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// 替换名称中不符合文件路径规则的字符为_
|
||||
/// </summary>
|
||||
/// <param name="fileName"></param>
|
||||
/// <returns></returns>
|
||||
public static string FileNameReplace(this string fileName)
|
||||
{
|
||||
// 定义文件名称规则的正则表达式模式
|
||||
string pattern = @"[^a-zA-Z0-9_./\\-]";
|
||||
// 使用正则表达式将不符合规则的部分替换为下划线
|
||||
string sanitizedFileName = Regex.Replace(fileName, pattern, "_");
|
||||
return sanitizedFileName;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// GetDebugLogBasePath
|
||||
/// </summary>
|
||||
public static string GetDebugLogBasePath()
|
||||
{
|
||||
return "Logs/DebugLog";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取DEBUG日志路径
|
||||
/// </summary>
|
||||
/// <param name="channelId"></param>
|
||||
/// <returns></returns>
|
||||
public static string GetDebugLogPath(this long channelId)
|
||||
{
|
||||
return GetDebugLogBasePath().CombinePath(channelId.ToString()).FileNameReplace();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// GetLogBasePath
|
||||
/// </summary>
|
||||
public static string GetLogBasePath()
|
||||
{
|
||||
return "Logs/ChannnelLog";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取日志路径
|
||||
/// </summary>
|
||||
/// <param name="channelId"></param>
|
||||
/// <returns></returns>
|
||||
public static string GetLogPath(this long channelId)
|
||||
{
|
||||
return GetLogBasePath().CombinePath(channelId.ToString()).FileNameReplace();
|
||||
}
|
||||
|
||||
#region 日志
|
||||
|
||||
/// <summary>
|
||||
/// 输出错误日志
|
||||
/// </summary>
|
||||
public static void LogError(this ILog logger, Exception ex, string msg)
|
||||
{
|
||||
logger.Log(TouchSocket.Core.LogLevel.Error, null, msg, ex);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 输出错误日志
|
||||
/// </summary>
|
||||
public static void LogError(this ILog logger, Exception ex)
|
||||
{
|
||||
logger.Log(TouchSocket.Core.LogLevel.Error, null, ex.Message, ex);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 输出提示日志
|
||||
/// </summary>
|
||||
public static void LogInformation(this ILog logger, string msg)
|
||||
{
|
||||
logger.Log(TouchSocket.Core.LogLevel.Info, null, msg, null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 输出Trace日志
|
||||
/// </summary>
|
||||
public static void LogTrace(this ILog logger, string msg)
|
||||
{
|
||||
logger.Log(TouchSocket.Core.LogLevel.Trace, null, msg, null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 输出警示日志
|
||||
/// </summary>
|
||||
public static void LogWarning(this ILog logger, Exception ex, string msg)
|
||||
{
|
||||
logger.Log(TouchSocket.Core.LogLevel.Warning, null, msg, ex);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 输出警示日志
|
||||
/// </summary>
|
||||
public static void LogWarning(this ILog logger, Exception ex)
|
||||
{
|
||||
logger.Log(TouchSocket.Core.LogLevel.Warning, null, ex.Message, ex);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 输出警示日志
|
||||
/// </summary>
|
||||
public static void LogWarning(this ILog logger, string msg)
|
||||
{
|
||||
logger.Log(TouchSocket.Core.LogLevel.Warning, null, msg, null);
|
||||
}
|
||||
|
||||
#endregion 日志
|
||||
}
|
||||
@@ -0,0 +1,257 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using System.Globalization;
|
||||
using System.Net;
|
||||
|
||||
using ThingsGateway.NewLife.Extension;
|
||||
|
||||
namespace ThingsGateway.Foundation.Extension.String;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public static class StringExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// 将字符串数组转换成字符串
|
||||
/// </summary>
|
||||
public static string ArrayToString(this string[] strArray, string separator = "")
|
||||
{
|
||||
if (strArray == null)
|
||||
return string.Empty;
|
||||
return string.Join(separator, strArray);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据<see cref="Type"/> 数据类型转化常见类型,如果不成功,返回false
|
||||
/// </summary>
|
||||
/// <param name="propertyType"></param>
|
||||
/// <param name="value"></param>
|
||||
/// <param name="objResult"></param>
|
||||
/// <returns></returns>
|
||||
public static bool GetTypeStringValue(this Type propertyType, object value, out string? objResult)
|
||||
{
|
||||
if (propertyType == typeof(bool))
|
||||
objResult = value.ToString();
|
||||
else if (propertyType == typeof(char))
|
||||
objResult = value.ToString();
|
||||
else if (propertyType == typeof(byte))
|
||||
objResult = value.ToString();
|
||||
else if (propertyType == typeof(sbyte))
|
||||
objResult = value.ToString();
|
||||
else if (propertyType == typeof(short))
|
||||
objResult = value.ToString();
|
||||
else if (propertyType == typeof(ushort))
|
||||
objResult = value.ToString();
|
||||
else if (propertyType == typeof(int))
|
||||
objResult = value.ToString();
|
||||
else if (propertyType == typeof(uint))
|
||||
objResult = value.ToString();
|
||||
else if (propertyType == typeof(long))
|
||||
objResult = value.ToString();
|
||||
else if (propertyType == typeof(ulong))
|
||||
objResult = value.ToString();
|
||||
else if (propertyType == typeof(float))
|
||||
objResult = value.ToString();
|
||||
else if (propertyType == typeof(double))
|
||||
objResult = value.ToString();
|
||||
else if (propertyType == typeof(decimal))
|
||||
objResult = value.ToString();
|
||||
else if (propertyType == typeof(DateTime))
|
||||
objResult = value.ToString();
|
||||
else if (propertyType == typeof(DateTimeOffset))
|
||||
objResult = value.ToString();
|
||||
else if (propertyType == typeof(string))
|
||||
objResult = value.ToString();
|
||||
else if (propertyType == typeof(IPAddress))
|
||||
objResult = value.ToString();
|
||||
else if (propertyType.IsEnum)
|
||||
objResult = value.ToString();
|
||||
else
|
||||
{
|
||||
objResult = null;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据<see cref="Type"/> 数据类型转化常见类型,如果不成功,返回false
|
||||
/// </summary>
|
||||
/// <param name="propertyType"></param>
|
||||
/// <param name="value"></param>
|
||||
/// <param name="objResult"></param>
|
||||
/// <returns></returns>
|
||||
public static bool GetTypeValue(this Type propertyType, string value, out object? objResult)
|
||||
{
|
||||
if (value == null)
|
||||
{
|
||||
if (propertyType.IsNullableType())
|
||||
{
|
||||
objResult = null;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (propertyType.IsNullableType())
|
||||
{
|
||||
propertyType = propertyType.GetGenericArguments()[0];
|
||||
}
|
||||
|
||||
if (propertyType == typeof(bool))
|
||||
objResult = value.ToBoolean(false);
|
||||
else if (propertyType == typeof(char))
|
||||
objResult = char.Parse(value);
|
||||
else if (propertyType == typeof(byte))
|
||||
objResult = byte.Parse(value);
|
||||
else if (propertyType == typeof(sbyte))
|
||||
objResult = sbyte.Parse(value);
|
||||
else if (propertyType == typeof(short))
|
||||
{
|
||||
if (value.StartsWith("0x", StringComparison.OrdinalIgnoreCase))
|
||||
objResult = short.Parse(value.Substring(2), NumberStyles.HexNumber);
|
||||
else
|
||||
objResult = short.Parse(value);
|
||||
}
|
||||
else if (propertyType == typeof(ushort))
|
||||
{
|
||||
if (value.StartsWith("0x", StringComparison.OrdinalIgnoreCase))
|
||||
objResult = ushort.Parse(value.Substring(2), NumberStyles.HexNumber);
|
||||
else
|
||||
objResult = ushort.Parse(value);
|
||||
}
|
||||
else if (propertyType == typeof(int))
|
||||
{
|
||||
if (value.StartsWith("0x", StringComparison.OrdinalIgnoreCase))
|
||||
objResult = int.Parse(value.Substring(2), NumberStyles.HexNumber);
|
||||
else
|
||||
objResult = int.Parse(value);
|
||||
}
|
||||
else if (propertyType == typeof(uint))
|
||||
{
|
||||
if (value.StartsWith("0x", StringComparison.OrdinalIgnoreCase))
|
||||
objResult = uint.Parse(value.Substring(2), NumberStyles.HexNumber);
|
||||
else
|
||||
objResult = uint.Parse(value);
|
||||
}
|
||||
else if (propertyType == typeof(long))
|
||||
{
|
||||
if (value.StartsWith("0x", StringComparison.OrdinalIgnoreCase))
|
||||
objResult = long.Parse(value.Substring(2), NumberStyles.HexNumber);
|
||||
else
|
||||
objResult = long.Parse(value);
|
||||
}
|
||||
else if (propertyType == typeof(ulong))
|
||||
{
|
||||
if (value.StartsWith("0x", StringComparison.OrdinalIgnoreCase))
|
||||
objResult = ulong.Parse(value.Substring(2), NumberStyles.HexNumber);
|
||||
else
|
||||
objResult = ulong.Parse(value);
|
||||
}
|
||||
else if (propertyType == typeof(float))
|
||||
objResult = float.Parse(value);
|
||||
else if (propertyType == typeof(double))
|
||||
objResult = double.Parse(value);
|
||||
else if (propertyType == typeof(decimal))
|
||||
objResult = decimal.Parse(value);
|
||||
else if (propertyType == typeof(DateTime))
|
||||
objResult = DateTime.Parse(value);
|
||||
else if (propertyType == typeof(DateTimeOffset))
|
||||
objResult = DateTimeOffset.Parse(value);
|
||||
else if (propertyType == typeof(string))
|
||||
objResult = value;
|
||||
else if (propertyType == typeof(IPAddress))
|
||||
objResult = IPAddress.Parse(value);
|
||||
else if (propertyType.IsEnum)
|
||||
objResult = Enum.Parse(propertyType, value);
|
||||
else
|
||||
{
|
||||
objResult = null;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <see cref="DataTransUtil.HexStringToBytes(string)"/>
|
||||
public static byte[] HexStringToBytes(this string str) => DataTransUtil.HexStringToBytes(str);
|
||||
|
||||
/// <summary>
|
||||
/// 根据英文逗号分割字符串,去除空白的字符
|
||||
/// </summary>
|
||||
public static string[]? SplitAndTrim(this string? str)
|
||||
{
|
||||
return str.Split(new char[1]
|
||||
{
|
||||
','
|
||||
}, StringSplitOptions.RemoveEmptyEntries);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据-符号分割字符串,去除空白的字符
|
||||
/// </summary>
|
||||
public static string[]? SplitByHyphen(this string? str)
|
||||
{
|
||||
return str.Split(new char[1]
|
||||
{
|
||||
'-'
|
||||
}, StringSplitOptions.RemoveEmptyEntries);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 只按第一个匹配项分割字符串
|
||||
/// </summary>
|
||||
/// <param name="str">要分割的字符串</param>
|
||||
/// <param name="split">分割字符</param>
|
||||
/// <returns>包含分割结果的列表</returns>
|
||||
public static List<string> SplitFirst(this string str, char split)
|
||||
{
|
||||
List<string> result = new List<string>();
|
||||
|
||||
// 寻找第一个分割字符的位置
|
||||
int index = str.IndexOf(split);
|
||||
if (index >= 0)
|
||||
{
|
||||
// 将第一个分割字符之前的部分添加到结果列表
|
||||
result.Add(str.Substring(0, index).Trim());
|
||||
// 将第一个分割字符之后的部分添加到结果列表
|
||||
result.Add(str.Substring(index + 1).Trim());
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据英文小数点进行分割字符串,去除空白的字符
|
||||
/// </summary>
|
||||
public static string[]? SplitStringByDelimiter(this string? str)
|
||||
{
|
||||
return str?.Split(new char[1]
|
||||
{
|
||||
'.'
|
||||
}, StringSplitOptions.RemoveEmptyEntries);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据英文分号分割字符串,去除空白的字符
|
||||
/// </summary>
|
||||
public static string[]? SplitStringBySemicolon(this string? str)
|
||||
{
|
||||
return str.Split(new char[1]
|
||||
{
|
||||
';'
|
||||
}, StringSplitOptions.RemoveEmptyEntries);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 返回List,无其他处理
|
||||
/// </summary>
|
||||
public static List<string> StringToList(this string str)
|
||||
{
|
||||
return new List<string>() { str };
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Reflection;
|
||||
|
||||
namespace ThingsGateway.Foundation.TypeExtension;
|
||||
|
||||
/// <summary>
|
||||
/// TypeExtension
|
||||
/// </summary>
|
||||
public static class TypeExtensions
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// IsNullable
|
||||
/// </summary>
|
||||
public static bool IsNullable(this PropertyInfo property)
|
||||
{
|
||||
#if NET6_0_OR_GREATER
|
||||
return new NullabilityInfoContext().Create(property).WriteState is NullabilityState.Nullable;
|
||||
#else
|
||||
return IsNullableHelper(property.PropertyType, property.DeclaringType, property.CustomAttributes);
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// IsNullable
|
||||
/// </summary>
|
||||
public static bool IsNullable(this FieldInfo field)
|
||||
{
|
||||
#if NET6_0_OR_GREATER
|
||||
return new NullabilityInfoContext().Create(field).WriteState is NullabilityState.Nullable;
|
||||
#else
|
||||
return IsNullableHelper(field.FieldType, field.DeclaringType, field.CustomAttributes);
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// IsNullable
|
||||
/// </summary>
|
||||
public static bool IsNullable(this ParameterInfo parameter)
|
||||
{
|
||||
#if NET6_0_OR_GREATER
|
||||
return new NullabilityInfoContext().Create(parameter).WriteState is NullabilityState.Nullable;
|
||||
#else
|
||||
return IsNullableHelper(parameter.ParameterType, parameter.Member, parameter.CustomAttributes);
|
||||
#endif
|
||||
}
|
||||
|
||||
private static bool IsNullableHelper(Type memberType, MemberInfo? declaringType, IEnumerable<CustomAttributeData> customAttributes)
|
||||
{
|
||||
if (memberType.IsValueType)
|
||||
return Nullable.GetUnderlyingType(memberType) != null;
|
||||
|
||||
var nullable = customAttributes
|
||||
.FirstOrDefault(x => x.AttributeType.FullName == "System.Runtime.CompilerServices.NullableAttribute");
|
||||
if (nullable != null && nullable.ConstructorArguments.Count == 1)
|
||||
{
|
||||
var attributeArgument = nullable.ConstructorArguments[0];
|
||||
if (attributeArgument.ArgumentType == typeof(byte[]))
|
||||
{
|
||||
var args = (ReadOnlyCollection<CustomAttributeTypedArgument>)attributeArgument.Value!;
|
||||
if (args.Count > 0 && args[0].ArgumentType == typeof(byte))
|
||||
{
|
||||
return (byte)args[0].Value! == 2;
|
||||
}
|
||||
}
|
||||
else if (attributeArgument.ArgumentType == typeof(byte))
|
||||
{
|
||||
return (byte)attributeArgument.Value! == 2;
|
||||
}
|
||||
}
|
||||
|
||||
for (var type = declaringType; type != null; type = type.DeclaringType)
|
||||
{
|
||||
var context = type.CustomAttributes
|
||||
.FirstOrDefault(x => x.AttributeType.FullName == "System.Runtime.CompilerServices.NullableContextAttribute");
|
||||
if (context != null &&
|
||||
context.ConstructorArguments.Count == 1 &&
|
||||
context.ConstructorArguments[0].ArgumentType == typeof(byte))
|
||||
{
|
||||
return (byte)context.ConstructorArguments[0].Value! == 2;
|
||||
}
|
||||
}
|
||||
|
||||
// Couldn't find a suitable attribute
|
||||
return false;
|
||||
}
|
||||
}
|
||||
12
src/Foundation/ThingsGateway.Foundation/GlobalUsings.cs
Normal file
12
src/Foundation/ThingsGateway.Foundation/GlobalUsings.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
global using TouchSocket.Core;
|
||||
global using TouchSocket.Sockets;
|
||||
@@ -0,0 +1,38 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using Microsoft.Extensions.Localization;
|
||||
|
||||
namespace ThingsGateway.Foundation;
|
||||
|
||||
/// <summary>
|
||||
/// 语言资源
|
||||
/// </summary>
|
||||
public class DefaultResource
|
||||
{
|
||||
private static IStringLocalizer localizer;
|
||||
|
||||
/// <summary>
|
||||
/// Localizer
|
||||
/// </summary>
|
||||
public static IStringLocalizer Localizer
|
||||
{
|
||||
get
|
||||
{
|
||||
if (localizer == null)
|
||||
{
|
||||
localizer = LocalizerUtil.GetLocalizer.Invoke(typeof(DefaultResource));
|
||||
}
|
||||
return localizer;
|
||||
}
|
||||
}
|
||||
|
||||
//使用频率高的多语言应初始化构建
|
||||
}
|
||||
56
src/Foundation/ThingsGateway.Foundation/Locales/en-US.json
Normal file
56
src/Foundation/ThingsGateway.Foundation/Locales/en-US.json
Normal file
@@ -0,0 +1,56 @@
|
||||
{
|
||||
"ThingsGateway.Foundation.DefaultResource": {
|
||||
"CannotSendIRequestInfo": "The current adapter does not support object sending",
|
||||
"CannotSet": "Not allowed to freely call {0} for assignment",
|
||||
"CannotSplicingSend": "This adapter does not support splicing sending",
|
||||
"CannotUseAdapterAgain": "This adapter has been used by other terminals, please recreate the object",
|
||||
"ConfigNotNull": "Configuration file cannot be null",
|
||||
"Connected": "Connection successful",
|
||||
"Connecting": "Connecting",
|
||||
"ConnectTimeout": "Connection timed out",
|
||||
"DataLengthError": "Data length error {0}",
|
||||
"DataTypeNotSupported": "{0} data type not implemented",
|
||||
"DefaultAddressDes": "————————————————————\n 4-byte data conversion format: data=ABCD; Optional ABCD=>Big-Endian; BADC=>Big-Endian Byte Swap; CDAB=>Little-Endian Byte Swap; DCBA=>Little-Endian.\n String length: len=1.\n Array length: arraylen=1.\n Bcd format: bcd=C8421, optional C8421; C5421; C2421; C3; Gray.\n Character format: encoding=UTF-8, optional UTF-8; ASCII; Default; Unicode, etc.\n————————————————————",
|
||||
"Disconnected": "Disconnected",
|
||||
"Disconnecting": "Disconnecting",
|
||||
"DtuConnected": "Dtu identifier {0} connection successful",
|
||||
"DtuNoConnectedWaining": "The Client(Dtu) is not connected, id: {0}",
|
||||
"ErrorMessage": "Error message",
|
||||
"EventError": "Error occurred in event {0}",
|
||||
"Exception": "Exception stack",
|
||||
"LengthShortError": "Data length is insufficient, original data: {0}",
|
||||
"NotActiveQueryError": "Data received correctly, but the host did not actively request data",
|
||||
"ProactivelyDisconnect": " {0} Proactively disconnect",
|
||||
"ProcessReceiveError": "Error occurred in receiving {0}, error code {1}",
|
||||
"Receive": "Receive",
|
||||
"ReceiveError": "Error occurred while processing data",
|
||||
"RemoteClose": "Remote terminal closed",
|
||||
"Send": "Send",
|
||||
"SerialPortNotClient": "The new SerialPort must be in connected state",
|
||||
"ServiceStarted": "Started",
|
||||
"ServiceStoped": "Stopped",
|
||||
"StringAddressError": "String read and write must specify length in register address, for example len=10;",
|
||||
"TransBytesError": "Conversion failed - original byte array {0}, length {1}",
|
||||
"UnknownError": "Unknown error, code: {0}",
|
||||
"StringTypePackError": "When the data type is string, the string length must be specified in order to pack it"
|
||||
},
|
||||
"ThingsGateway.Foundation.ChannelData": {
|
||||
"Name": "Name",
|
||||
"Name.Required": "{0} cannot be empty",
|
||||
"ChannelType": "ChannelType",
|
||||
|
||||
"RemoteUrl": "RemoteUrl",
|
||||
"BindUrl": "BindUrl",
|
||||
|
||||
"PortName": "PortName",
|
||||
"BaudRate": "BaudRate",
|
||||
"DataBits": "DataBits",
|
||||
"Parity": "Parity",
|
||||
"StopBits": "StopBits",
|
||||
"DtrEnable": "DtrEnable",
|
||||
"RtsEnable": "RtsEnable"
|
||||
},
|
||||
"ThingsGateway.Foundation.VariableClass": {
|
||||
"RegisterAddress": "RegisterAddress"
|
||||
}
|
||||
}
|
||||
57
src/Foundation/ThingsGateway.Foundation/Locales/zh-CN.json
Normal file
57
src/Foundation/ThingsGateway.Foundation/Locales/zh-CN.json
Normal file
@@ -0,0 +1,57 @@
|
||||
{
|
||||
"ThingsGateway.Foundation.DefaultResource": {
|
||||
"CannotSendIRequestInfo": "当前适配器不支持对象发送",
|
||||
"CannotSet": "不允许自由调用 {0} 进行赋值",
|
||||
"CannotSplicingSend": "该适配器不支持拼接发送",
|
||||
"CannotUseAdapterAgain": "此适配器已被其他终端使用,请重新创建对象",
|
||||
"ConfigNotNull": "配置文件不能为空",
|
||||
"Connected": "连接成功",
|
||||
"Connecting": "正在连接",
|
||||
"ConnectTimeout": "连接超时",
|
||||
"DataLengthError": "数据长度错误 {0}",
|
||||
"DataTypeNotSupported": "{0} 数据类型未实现",
|
||||
"DefaultAddressDes": "————————————————————\n 4字节数据转换格式:data=ABCD;可选ABCD=>Big-Endian;BADC=>Big-Endian Byte Swap;CDAB=>Little-Endian Byte Swap;DCBA=>Little-Endian。\n 字符串长度:len=1。\n 数组长度:arraylen=1。\n Bcd格式:bcd=C8421,可选C8421;C5421;C2421;C3;Gray。\n 字符格式:encoding=UTF-8,可选UTF-8;ASCII;Default;Unicode等。\n————————————————————",
|
||||
"Disconnected": "断开连接",
|
||||
"Disconnecting": "正在断开连接",
|
||||
"DtuConnected": "Dtu标识 {0} 连接成功",
|
||||
"DtuNoConnectedWaining": "客户端(Dtu)未连接,id:{0}",
|
||||
"ErrorMessage": "错误信息",
|
||||
"EventError": "在事件 {0} 中发生错误",
|
||||
"Exception": "异常堆栈",
|
||||
"LengthShortError": "数据长度不足,原始数据:{0}",
|
||||
"NotActiveQueryError": "接收数据正确,但主机并没有主动请求数据",
|
||||
"ProactivelyDisconnect": " {0} 主动断开",
|
||||
"ProcessReceiveError": "接收出现错误 {0},错误代码 {1}",
|
||||
"Receive": "接收",
|
||||
"ReceiveError": "在处理数据时发生错误",
|
||||
"RemoteClose": "远程终端已关闭",
|
||||
"Send": "发送",
|
||||
"SerialPortNotClient": "新的SerialPort必须在连接状态",
|
||||
"ServiceStarted": "启动",
|
||||
"ServiceStoped": "停止",
|
||||
"StringAddressError": "字符串读写必须在寄存器地址中指定长度,例如 len=10;",
|
||||
"TransBytesError": "转换失败-原始字节数组 {0},长度 {1}",
|
||||
"UnknownError": "未知错误,错误代码:{0}",
|
||||
"StringTypePackError": "数据类型为字符串时,必须指定字符串长度,才能进行打包"
|
||||
},
|
||||
|
||||
"ThingsGateway.Foundation.ChannelData": {
|
||||
"Name": "名称",
|
||||
"Name.Required": " {0} 不可为空",
|
||||
"ChannelType": "通道类型",
|
||||
|
||||
"RemoteUrl": "远程IP地址",
|
||||
"BindUrl": "本地绑定IP地址",
|
||||
|
||||
"PortName": "COM口",
|
||||
"BaudRate": "波特率",
|
||||
"DataBits": "数据位",
|
||||
"Parity": "校验位",
|
||||
"StopBits": "停止位",
|
||||
"DtrEnable": "Dtr",
|
||||
"RtsEnable": "Rts"
|
||||
},
|
||||
"ThingsGateway.Foundation.VariableClass": {
|
||||
"RegisterAddress": "寄存器地址"
|
||||
}
|
||||
}
|
||||
55
src/Foundation/ThingsGateway.Foundation/Locales/zh-TW.json
Normal file
55
src/Foundation/ThingsGateway.Foundation/Locales/zh-TW.json
Normal file
@@ -0,0 +1,55 @@
|
||||
{
|
||||
"ThingsGateway.Foundation.DefaultResource": {
|
||||
"CannotSendIRequestInfo": "當前適配器不支援物件發送",
|
||||
"CannotSet": "不允許自由調用 {0} 進行賦值",
|
||||
"CannotSplicingSend": "該適配器不支援拼接發送",
|
||||
"CannotUseAdapterAgain": "此適配器已被其他終端使用,請重新創建物件",
|
||||
"ConfigNotNull": "配置文件不能為空",
|
||||
"Connected": "連接成功",
|
||||
"Connecting": "正在連接",
|
||||
"ConnectTimeout": "連接超時",
|
||||
"DataLengthError": "數據長度錯誤 {0}",
|
||||
"DataTypeNotSupported": "{0} 數據類型未實現",
|
||||
"DefaultAddressDes": "————————————————————\n 4字節數據轉換格式:data=ABCD;可選ABCD=>Big-Endian;BADC=>Big-Endian Byte Swap;CDAB=>Little-Endian Byte Swap;DCBA=>Little-Endian。\n 字串長度:len=1。\n 陣列長度:arraylen=1。\n Bcd格式:bcd=C8421,可選C8421;C5421;C2421;C3;Gray。\n 字元格式:encoding=UTF-8,可選UTF-8;ASCII;Default;Unicode等。\n————————————————————",
|
||||
"Disconnected": "斷開連接",
|
||||
"Disconnecting": "正在斷開連接",
|
||||
"DtuConnected": "Dtu標識 {0} 連接成功",
|
||||
"DtuNoConnectedWaining": "客戶端(Dtu)未連接,id:{0}",
|
||||
"ErrorMessage": "錯誤資訊",
|
||||
"EventError": "在事件 {0} 中發生錯誤",
|
||||
"Exception": "異常堆疊",
|
||||
"LengthShortError": "數據長度不足,原始數據:{0}",
|
||||
"NotActiveQueryError": "接收數據正確,但主機並沒有主動請求數據",
|
||||
"ProactivelyDisconnect": " {0} 主動斷開",
|
||||
"ProcessReceiveError": "接收出現錯誤 {0},錯誤代碼 {1}",
|
||||
"Receive": "接收",
|
||||
"ReceiveError": "在處理數據時發生錯誤",
|
||||
"RemoteClose": "遠程終端已關閉",
|
||||
"Send": "發送",
|
||||
"SerialPortNotClient": "新的SerialPort必須在連接狀態",
|
||||
"ServiceStarted": "啟動",
|
||||
"ServiceStoped": "停止",
|
||||
"StringAddressError": "字串讀寫必須在寄存器地址中指定長度,例如 len=10;",
|
||||
"TransBytesError": "轉換失敗-原始字節數組 {0},長度 {1}",
|
||||
"UnknownError": "未知錯誤,錯誤代碼:{0}",
|
||||
"StringTypePackError": "數據類型為字串時,必須指定字串長度,才能進行打包"
|
||||
},
|
||||
|
||||
"ThingsGateway.Foundation.ChannelData": {
|
||||
"Name": "名稱",
|
||||
"Name.Required": " {0} 不可為空",
|
||||
"ChannelType": "通道類型",
|
||||
"RemoteUrl": "遠程IP地址",
|
||||
"BindUrl": "本地綁定IP地址",
|
||||
"PortName": "COM口",
|
||||
"BaudRate": "波特率",
|
||||
"DataBits": "數據位",
|
||||
"Parity": "校驗位",
|
||||
"StopBits": "停止位",
|
||||
"DtrEnable": "Dtr",
|
||||
"RtsEnable": "Rts"
|
||||
},
|
||||
"ThingsGateway.Foundation.VariableClass": {
|
||||
"RegisterAddress": "寄存器地址"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using Microsoft.Extensions.Localization;
|
||||
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
using System.Collections.Concurrent;
|
||||
using System.Globalization;
|
||||
using System.Text;
|
||||
|
||||
namespace ThingsGateway.Foundation;
|
||||
|
||||
/// <summary>
|
||||
/// json资源多语言
|
||||
/// </summary>
|
||||
public class JsonLocalizer : IStringLocalizer
|
||||
{
|
||||
private readonly ConcurrentDictionary<string, JObject> _resources = new();
|
||||
private string _folderName;
|
||||
private Type _type;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public JsonLocalizer(Type resourceType, string folderName)
|
||||
{
|
||||
_type = resourceType;
|
||||
_folderName = folderName;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public LocalizedString this[string name]
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_resources.TryGetValue(CultureInfo.CurrentUICulture.Name, out var resource))
|
||||
{
|
||||
if (resource.TryGetValue(name, out JToken value))
|
||||
{
|
||||
return new LocalizedString(name, value.ToString());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (Add(_type, _folderName))
|
||||
{
|
||||
if (_resources.TryGetValue(CultureInfo.CurrentUICulture.Name, out var resource1))
|
||||
{
|
||||
if (resource1.TryGetValue(name, out JToken value))
|
||||
{
|
||||
return new LocalizedString(name, value.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new LocalizedString(name, name, true);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public LocalizedString this[string name, params object[] arguments]
|
||||
{
|
||||
get
|
||||
{
|
||||
string value = this[name].Value;
|
||||
return new LocalizedString(name, string.Format(value, arguments));
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IEnumerable<LocalizedString> GetAllStrings(bool includeParentCultures)
|
||||
{
|
||||
if (_resources.TryGetValue(CultureInfo.CurrentUICulture.Name, out var resource))
|
||||
{
|
||||
foreach (var item in resource)
|
||||
{
|
||||
yield return new LocalizedString(item.Key, item.Value.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool Add(Type resourceType, string folderName)
|
||||
{
|
||||
try
|
||||
{
|
||||
var assembly = resourceType.Assembly;
|
||||
var culture = CultureInfo.CurrentUICulture.Name;
|
||||
using var stream = assembly.GetManifestResourceStream($@"{assembly.GetName().Name}.{folderName}.{culture}.json");
|
||||
using var reader = new StreamReader(stream, Encoding.UTF8);
|
||||
var jsonData = reader.ReadToEnd();
|
||||
|
||||
var resource = (JObject.Parse(jsonData)[resourceType.FullName] as JObject)!;
|
||||
_resources.TryAdd(culture, resource);
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
137
src/Foundation/ThingsGateway.Foundation/Logger/TextFileLogger.cs
Normal file
137
src/Foundation/ThingsGateway.Foundation/Logger/TextFileLogger.cs
Normal file
@@ -0,0 +1,137 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using System.Text;
|
||||
|
||||
using ThingsGateway.NewLife.Caching;
|
||||
|
||||
namespace ThingsGateway.Foundation;
|
||||
|
||||
/// <summary>
|
||||
/// 文本文件日志类。提供向文本文件写日志的能力
|
||||
/// </summary>
|
||||
public class TextFileLogger : ThingsGateway.NewLife.Log.TextFileLog, TouchSocket.Core.ILog, IDisposable
|
||||
{
|
||||
private static string separator = Environment.NewLine + "-----ThingsGateway-Log-Separator-----" + Environment.NewLine;
|
||||
|
||||
/// <summary>
|
||||
/// 分隔符
|
||||
/// </summary>
|
||||
public static string Separator
|
||||
{
|
||||
get
|
||||
{
|
||||
return separator;
|
||||
}
|
||||
set
|
||||
{
|
||||
separator = value;
|
||||
separatorBytes = Encoding.UTF8.GetBytes(separator);
|
||||
}
|
||||
}
|
||||
private static byte[] separatorBytes = Encoding.UTF8.GetBytes(Environment.NewLine + "-----ThingsGateway-Log-Separator-----" + Environment.NewLine);
|
||||
|
||||
internal static byte[] SeparatorBytes
|
||||
{
|
||||
get
|
||||
{
|
||||
return separatorBytes;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static readonly MemoryCache cache = new MemoryCache();
|
||||
|
||||
/// <summary>
|
||||
/// 文本日志记录器
|
||||
/// </summary>
|
||||
/// <param name="path">路径</param>
|
||||
/// <param name="isfile">单文件</param>
|
||||
/// <param name="fileFormat">文件名称格式</param>
|
||||
private TextFileLogger(string path, bool isfile, string? fileFormat = null) : base(path, isfile, fileFormat)
|
||||
{
|
||||
_headEnable = false;
|
||||
}
|
||||
|
||||
/// <summary>每个目录的日志实例应该只有一个,所以采用静态创建</summary>
|
||||
/// <param name="path">日志目录或日志文件路径</param>
|
||||
/// <param name="fileFormat"></param>
|
||||
/// <returns></returns>
|
||||
public static TextFileLogger CreateTextLogger(String path, String? fileFormat = null)
|
||||
{
|
||||
//if (path.IsNullOrEmpty()) path = XTrace.LogPath;
|
||||
if (path.IsNullOrEmpty()) path = "Log";
|
||||
|
||||
var key = (path + fileFormat).ToLower();
|
||||
return cache.GetOrAdd(key, k => new TextFileLogger(path, false, fileFormat));
|
||||
}
|
||||
|
||||
/// <summary>每个目录的日志实例应该只有一个,所以采用静态创建</summary>
|
||||
/// <param name="path">日志目录或日志文件路径</param>
|
||||
/// <returns></returns>
|
||||
public static TextFileLogger CreateTextFileLogger(String path)
|
||||
{
|
||||
if (path.IsNullOrEmpty()) throw new ArgumentNullException(nameof(path));
|
||||
|
||||
return cache.GetOrAdd(path, k => new TextFileLogger(k, true));
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// TimeFormat
|
||||
/// </summary>
|
||||
public const string TimeFormat = "yyyy-MM-dd HH:mm:ss:ffff zz";
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc/>
|
||||
/// </summary>
|
||||
/// <param name="logLevel"></param>
|
||||
/// <param name="source"></param>
|
||||
/// <param name="message"></param>
|
||||
/// <param name="exception"></param>
|
||||
protected void WriteLog(LogLevel logLevel, object source, string message, Exception exception)
|
||||
{
|
||||
if (!Check()) return;
|
||||
|
||||
var stringBuilder = new StringBuilder();
|
||||
stringBuilder.Append(DateTime.Now.ToString(TimeFormat));
|
||||
stringBuilder.Append(",");
|
||||
stringBuilder.Append(logLevel.ToString());
|
||||
stringBuilder.Append(",");
|
||||
stringBuilder.Append($"\"{message}\"");
|
||||
|
||||
if (exception != null)
|
||||
{
|
||||
stringBuilder.Append(",");
|
||||
stringBuilder.Append($"\"{exception}\"");
|
||||
}
|
||||
|
||||
//自定义的分割符,用于读取文件时的每一行判断,而不是单纯换行符
|
||||
stringBuilder.Append(Separator);
|
||||
|
||||
// 推入队列
|
||||
Enqueue(stringBuilder.ToString());
|
||||
|
||||
WriteLog();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public LogLevel LogLevel { get; set; } = LogLevel.Trace;
|
||||
/// <inheritdoc/>
|
||||
public void Log(LogLevel logLevel, object source, string message, Exception exception)
|
||||
{
|
||||
if (logLevel < LogLevel)
|
||||
{
|
||||
return;
|
||||
}
|
||||
WriteLog(logLevel, source, message, exception);
|
||||
}
|
||||
|
||||
}
|
||||
338
src/Foundation/ThingsGateway.Foundation/Logger/TextFileReader.cs
Normal file
338
src/Foundation/ThingsGateway.Foundation/Logger/TextFileReader.cs
Normal file
@@ -0,0 +1,338 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using System.Text;
|
||||
|
||||
namespace ThingsGateway.Foundation;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public class LastLogResult : OperResultClass<IEnumerable<LogData>>
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public LastLogResult() : base()
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public LastLogResult(IOperResult operResult) : base(operResult)
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public LastLogResult(string msg) : base(msg)
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public LastLogResult(Exception ex) : base(ex)
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public LastLogResult(string msg, Exception ex) : base(msg, ex)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 流位置
|
||||
/// </summary>
|
||||
public long Position { set; get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 日志数据
|
||||
/// </summary>
|
||||
public class LogData
|
||||
{
|
||||
/// <summary>
|
||||
/// 异常
|
||||
/// </summary>
|
||||
public string? ExceptionString { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 级别
|
||||
/// </summary>
|
||||
public LogLevel LogLevel { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 时间
|
||||
/// </summary>
|
||||
public string LogTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 消息
|
||||
/// </summary>
|
||||
public string Message { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc/>
|
||||
/// </summary>
|
||||
public class LogInfoResult : OperResultClass
|
||||
{
|
||||
/// <summary>
|
||||
/// 全名称
|
||||
/// </summary>
|
||||
public string FullName { set; get; }
|
||||
|
||||
/// <summary>
|
||||
/// 流位置
|
||||
/// </summary>
|
||||
public long Length { set; get; }
|
||||
}
|
||||
|
||||
/// <summary>日志文本文件倒序读取</summary>
|
||||
public class TextFileReader
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取指定目录下所有文件信息
|
||||
/// </summary>
|
||||
/// <param name="directoryPath">目录路径</param>
|
||||
/// <returns>包含文件信息的列表</returns>
|
||||
public static List<LogInfoResult> GetFiles(string directoryPath)
|
||||
{
|
||||
// 检查目录是否存在
|
||||
if (!Directory.Exists(directoryPath))
|
||||
{
|
||||
return new List<LogInfoResult>();
|
||||
}
|
||||
|
||||
// 获取目录下所有文件路径
|
||||
var files = Directory.GetFiles(directoryPath);
|
||||
|
||||
// 如果文件列表为空,则返回空列表
|
||||
if (files == null || files.Length == 0)
|
||||
{
|
||||
return new List<LogInfoResult>();
|
||||
}
|
||||
|
||||
// 获取文件信息并按照最后写入时间降序排序
|
||||
var fileInfos = files.Select(filePath => new FileInfo(filePath))
|
||||
.OrderByDescending(x => x.LastWriteTime)
|
||||
.Select(x => new LogInfoResult() { FullName = x.FullName, Length = x.Length })
|
||||
.ToList();
|
||||
|
||||
return fileInfos;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从指定位置开始倒序读取文本文件,并解析日志数据
|
||||
/// </summary>
|
||||
/// <param name="file">文件路径</param>
|
||||
/// <param name="position">读取流的起始位置,如果为0,则从文件末尾开始读取</param>
|
||||
/// <param name="lineCount">读取行数,默认为200行</param>
|
||||
/// <returns>返回最后读取的日志内容、新的起始位置和操作状态</returns>
|
||||
public static LastLogResult LastLog(string file, long position, int lineCount = 200)
|
||||
{
|
||||
LastLogResult result = new(); // 初始化结果对象
|
||||
try
|
||||
{
|
||||
if (!File.Exists(file)) // 检查文件是否存在
|
||||
{
|
||||
result.OperCode = 999;
|
||||
result.ErrorMessage = "The file path is invalid";
|
||||
return result;
|
||||
}
|
||||
|
||||
List<string> txt = new(); // 存储读取的文本内容
|
||||
long ps = position; // 保存起始位置
|
||||
|
||||
using (var fs = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
|
||||
{
|
||||
if (ps <= 0) // 如果起始位置小于等于0,将起始位置设置为文件长度
|
||||
ps = fs.Length;
|
||||
|
||||
// 循环读取指定行数的文本内容
|
||||
for (int i = 0; i < lineCount; i++)
|
||||
{
|
||||
ps = InverseReadRow(fs, ps, txt); // 调用方法逆向读取一行文本并存储
|
||||
if (ps <= 0) // 如果已经读取到文件开头则跳出循环
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 解析读取的文本为日志数据
|
||||
result.Content = txt.Select(a =>
|
||||
{
|
||||
var data = ParseCSV(a); // 解析CSV格式的文本
|
||||
if (data.Count > 2) // 如果解析出的数据列数大于2,则认为是有效日志数据
|
||||
{
|
||||
var log = new LogData(); // 创建日志数据对象
|
||||
log.LogTime = data[0].Trim(); // 日志时间
|
||||
log.LogLevel = (LogLevel)Enum.Parse(typeof(LogLevel), data[1].Trim()); // 日志级别
|
||||
log.Message = data[2].Trim(); // 日志消息
|
||||
log.ExceptionString = data.Count > 3 ? data[3].Trim() : null; // 异常信息(如果有)
|
||||
return log; // 返回解析后的日志数据
|
||||
}
|
||||
else
|
||||
{
|
||||
return null; // 数据列数不足2则返回空
|
||||
}
|
||||
}).Where(a => a != null).ToList()!; // 过滤空值并转换为列表
|
||||
|
||||
result.Position = ps; // 更新起始位置
|
||||
result.OperCode = 0; // 操作状态设为成功
|
||||
return result; // 返回解析结果
|
||||
}
|
||||
catch (Exception ex) // 捕获异常
|
||||
{
|
||||
result = new LastLogResult(ex); // 创建包含异常信息的结果对象
|
||||
return result; // 返回异常结果
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从后向前按行读取文本文件,并根据特定规则筛选行数据
|
||||
/// </summary>
|
||||
/// <param name="fs">文件流</param>
|
||||
/// <param name="position">读取位置</param>
|
||||
/// <param name="strs">存储读取的文本行</param>
|
||||
/// <param name="maxRead">每次最多读取字节数,默认为10kb</param>
|
||||
/// <returns>返回新的读取位置</returns>
|
||||
private static long InverseReadRow(FileStream fs, long position, List<string> strs, int maxRead = 10240)
|
||||
{
|
||||
byte n = 0xD; // 换行符
|
||||
byte a = 0xA; // 回车符
|
||||
|
||||
if (fs.Length == 0) return 0; // 若文件长度为0,则直接返回0作为新的位置
|
||||
|
||||
var newPos = position - 1; // 新的位置从指定位置减一开始
|
||||
|
||||
try
|
||||
{
|
||||
int curVal;
|
||||
var readLength = 0;
|
||||
LinkedList<byte> arr = new LinkedList<byte>(); // 存储读取的字节数据
|
||||
|
||||
while (true) // 循环读取一行数据,TextFileLogger.Separator行判定
|
||||
{
|
||||
readLength++;
|
||||
if (newPos <= 0)
|
||||
newPos = 0;
|
||||
|
||||
fs.Position = newPos;
|
||||
curVal = fs.ReadByte();
|
||||
if (curVal == -1) break; // 到达文件开头时跳出循环
|
||||
|
||||
arr.AddFirst((byte)curVal); // 将读取的字节插入列表头部
|
||||
|
||||
if (curVal == n || curVal == a)//判断当前字符是换行符 // TextFileLogger.Separator
|
||||
{
|
||||
if (MatchSeparator(arr))
|
||||
{
|
||||
// 去掉匹配的指定字符串
|
||||
for (int i = 0; i < TextFileLogger.SeparatorBytes.Length; i++)
|
||||
{
|
||||
arr.RemoveFirst();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (readLength == maxRead) // 达到最大读取限制时,直接放弃
|
||||
{
|
||||
arr = new();
|
||||
readLength = arr.Count;
|
||||
}
|
||||
newPos--;
|
||||
if (newPos <= -1)
|
||||
break;
|
||||
}
|
||||
|
||||
if (arr.Count >= 5) // 处理完整的行数据
|
||||
{
|
||||
var str = Encoding.UTF8.GetString(arr.ToArray()); // 转换为字符串
|
||||
strs.Add(str); // 存储有效行数据
|
||||
}
|
||||
|
||||
return newPos; // 返回新的读取位置
|
||||
}
|
||||
finally
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
private static bool MatchSeparator(LinkedList<byte> arr)
|
||||
{
|
||||
if (arr.Count < TextFileLogger.SeparatorBytes.Length)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var currentNode = arr.First; // 从头节点开始
|
||||
for (int i = 0; i < TextFileLogger.SeparatorBytes.Length; i++)
|
||||
{
|
||||
if (currentNode.Value != TextFileLogger.SeparatorBytes[i])
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
currentNode = currentNode.Next; // 移动到下一个节点
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static List<string> ParseCSV(string data)
|
||||
{
|
||||
List<string> items = new List<string>();
|
||||
|
||||
int i = 0;
|
||||
while (i < data.Length)
|
||||
{
|
||||
// 当前字符不是逗号,开始解析一个新的数据项
|
||||
if (data[i] != ',')
|
||||
{
|
||||
int j = i;
|
||||
bool inQuotes = false;
|
||||
|
||||
// 解析到一个未闭合的双引号时,继续读取下一个数据项
|
||||
while (j < data.Length && (inQuotes || data[j] != ','))
|
||||
{
|
||||
if (data[j] == '\"')
|
||||
{
|
||||
inQuotes = !inQuotes;
|
||||
}
|
||||
j++;
|
||||
}
|
||||
|
||||
// 去掉前后的双引号并将当前数据项加入列表中
|
||||
items.Add(RemoveQuotes(data.Substring(i, j - i)));
|
||||
|
||||
// 跳过当前数据项结尾的逗号
|
||||
if (j < data.Length && data[j] == ',')
|
||||
{
|
||||
j++;
|
||||
}
|
||||
|
||||
i = j;
|
||||
}
|
||||
// 当前字符是逗号,跳过它
|
||||
else
|
||||
{
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
private static string RemoveQuotes(string data)
|
||||
{
|
||||
if (data.Length >= 2 && data[0] == '\"' && data[data.Length - 1] == '\"')
|
||||
{
|
||||
return data.Substring(1, data.Length - 2);
|
||||
}
|
||||
else
|
||||
{
|
||||
return data;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace ThingsGateway.Foundation;
|
||||
|
||||
/// <summary>
|
||||
/// 操作返回类型
|
||||
/// </summary>
|
||||
public enum ErrorCodeEnum
|
||||
{
|
||||
/// <summary>
|
||||
/// 设备返回错误,默认失败类型
|
||||
/// </summary>
|
||||
RetuenError,
|
||||
|
||||
/// <summary>
|
||||
/// 执行失败报错
|
||||
/// </summary>
|
||||
InvokeFail,
|
||||
|
||||
/// <summary>
|
||||
/// 超时取消
|
||||
/// </summary>
|
||||
Canceled,
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace ThingsGateway.Foundation;
|
||||
|
||||
/// <summary>
|
||||
/// 操作接口,继承<see cref="IRequestInfo"/>后可直接用于适配器
|
||||
/// </summary>
|
||||
public interface IOperResult : IRequestInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// 执行错误返回类型
|
||||
/// </summary>
|
||||
ErrorCodeEnum? ErrorCode { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 返回消息
|
||||
/// </summary>
|
||||
string? ErrorMessage { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 异常堆栈
|
||||
/// </summary>
|
||||
public Exception? Exception { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否成功
|
||||
/// </summary>
|
||||
bool IsSuccess { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 错误代码,不为0时判定为失败
|
||||
/// </summary>
|
||||
int? OperCode { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 操作接口
|
||||
/// </summary>
|
||||
public interface IOperResult<out T> : IOperResult
|
||||
{
|
||||
/// <summary>
|
||||
/// 返回对象
|
||||
/// </summary>
|
||||
T Content { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 操作接口
|
||||
/// </summary>
|
||||
public interface IOperResult<out T, out T2> : IOperResult<T>
|
||||
{
|
||||
/// <summary>
|
||||
/// 返回对象
|
||||
/// </summary>
|
||||
T2 Content2 { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 操作接口
|
||||
/// </summary>
|
||||
public interface IOperResult<out T, out T2, out T3> : IOperResult<T, T2>
|
||||
{
|
||||
/// <summary>
|
||||
/// 返回对象
|
||||
/// </summary>
|
||||
T3 Content3 { get; }
|
||||
}
|
||||
602
src/Foundation/ThingsGateway.Foundation/OperResult/OperResult.cs
Normal file
602
src/Foundation/ThingsGateway.Foundation/OperResult/OperResult.cs
Normal file
@@ -0,0 +1,602 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Converters;
|
||||
|
||||
namespace ThingsGateway.Foundation;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public struct OperResult<T> : IOperResult<T>
|
||||
{
|
||||
/// <summary>
|
||||
/// 异常堆栈
|
||||
/// </summary>
|
||||
#if NET6_0_OR_GREATER
|
||||
[System.Text.Json.Serialization.JsonIgnore]
|
||||
#endif
|
||||
|
||||
[JsonIgnore]
|
||||
public Exception? Exception { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 默认构造,操作结果会是成功
|
||||
/// </summary>
|
||||
public OperResult()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从另一个操作对象中赋值信息
|
||||
/// </summary>
|
||||
public OperResult(IOperResult operResult)
|
||||
{
|
||||
OperCode = operResult.OperCode;
|
||||
ErrorMessage = operResult.ErrorMessage;
|
||||
Exception = operResult.Exception;
|
||||
ErrorCode = operResult.ErrorCode;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 传入错误信息
|
||||
/// </summary>
|
||||
/// <param name="msg"></param>
|
||||
public OperResult(string msg)
|
||||
{
|
||||
OperCode = 500;
|
||||
ErrorMessage = msg;
|
||||
ErrorCode = ErrorCodeEnum.InvokeFail;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 传入异常堆栈
|
||||
/// </summary>
|
||||
public OperResult(Exception ex)
|
||||
{
|
||||
OperCode = 500;
|
||||
Exception = ex;
|
||||
ErrorMessage = ex.Message;
|
||||
//指定Timeout或OperationCanceled为超时取消
|
||||
if (ex is TimeoutException || ex is OperationCanceledException)
|
||||
{
|
||||
ErrorCode = ErrorCodeEnum.Canceled;
|
||||
}
|
||||
else if (ex is ReturnErrorException)
|
||||
{
|
||||
ErrorCode = ErrorCodeEnum.RetuenError;
|
||||
}
|
||||
else
|
||||
{
|
||||
ErrorCode = ErrorCodeEnum.InvokeFail;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 传入错误信息与异常堆栈
|
||||
/// </summary>
|
||||
public OperResult(string msg, Exception ex) : this(ex)
|
||||
{
|
||||
ErrorMessage = msg;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public int? OperCode { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool IsSuccess => OperCode == null || OperCode == 0;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string? ErrorMessage { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
#if NET6_0_OR_GREATER
|
||||
[System.Text.Json.Serialization.JsonConverter(typeof(System.Text.Json.Serialization.JsonStringEnumConverter))]
|
||||
#endif
|
||||
|
||||
[JsonConverter(typeof(StringEnumConverter))]
|
||||
public ErrorCodeEnum? ErrorCode { get; private set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public T Content { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 返回错误信息与异常堆栈等信息
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public override string ToString()
|
||||
{
|
||||
string messageString = ErrorMessage == null ? string.Empty : $"{DefaultResource.Localizer["ErrorMessage"]}:{ErrorMessage}";
|
||||
string exceptionString = Exception == null ? string.Empty : ErrorMessage == null ? $"{DefaultResource.Localizer["Exception"]}:{Exception}" : $"{Environment.NewLine}{DefaultResource.Localizer["Exception"]}:{Exception}";
|
||||
|
||||
return $"{messageString}{exceptionString}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 返回OperResult
|
||||
/// </summary>
|
||||
/// <param name="operResult"></param>
|
||||
public static implicit operator OperResult(OperResult<T> operResult)
|
||||
{
|
||||
return new OperResult(operResult);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public struct OperResult<T, T2> : IOperResult<T, T2>
|
||||
{
|
||||
/// <summary>
|
||||
/// 异常堆栈
|
||||
/// </summary>
|
||||
#if NET6_0_OR_GREATER
|
||||
[System.Text.Json.Serialization.JsonIgnore]
|
||||
#endif
|
||||
|
||||
[JsonIgnore]
|
||||
public Exception? Exception { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 默认构造,操作结果会是成功
|
||||
/// </summary>
|
||||
public OperResult()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从另一个操作对象中赋值信息
|
||||
/// </summary>
|
||||
public OperResult(IOperResult operResult)
|
||||
{
|
||||
OperCode = operResult.OperCode;
|
||||
ErrorMessage = operResult.ErrorMessage;
|
||||
Exception = operResult.Exception;
|
||||
ErrorCode = operResult.ErrorCode;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 传入错误信息
|
||||
/// </summary>
|
||||
/// <param name="msg"></param>
|
||||
public OperResult(string msg)
|
||||
{
|
||||
OperCode = 500;
|
||||
ErrorMessage = msg;
|
||||
ErrorCode = ErrorCodeEnum.InvokeFail;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 传入异常堆栈
|
||||
/// </summary>
|
||||
public OperResult(Exception ex)
|
||||
{
|
||||
OperCode = 500;
|
||||
Exception = ex;
|
||||
ErrorMessage = ex.Message;
|
||||
//指定Timeout或OperationCanceled为超时取消
|
||||
if (ex is TimeoutException || ex is OperationCanceledException)
|
||||
{
|
||||
ErrorCode = ErrorCodeEnum.Canceled;
|
||||
}
|
||||
else if (ex is ReturnErrorException)
|
||||
{
|
||||
ErrorCode = ErrorCodeEnum.RetuenError;
|
||||
}
|
||||
else
|
||||
{
|
||||
ErrorCode = ErrorCodeEnum.InvokeFail;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 传入错误信息与异常堆栈
|
||||
/// </summary>
|
||||
public OperResult(string msg, Exception ex) : this(ex)
|
||||
{
|
||||
ErrorMessage = msg;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public int? OperCode { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool IsSuccess => OperCode == null || OperCode == 0;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string? ErrorMessage { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
#if NET6_0_OR_GREATER
|
||||
[System.Text.Json.Serialization.JsonConverter(typeof(System.Text.Json.Serialization.JsonStringEnumConverter))]
|
||||
#endif
|
||||
|
||||
[JsonConverter(typeof(StringEnumConverter))]
|
||||
public ErrorCodeEnum? ErrorCode { get; private set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public T2 Content2 { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public T Content { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 返回错误信息与异常堆栈等信息
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public override string ToString()
|
||||
{
|
||||
string messageString = ErrorMessage == null ? string.Empty : $"{DefaultResource.Localizer["ErrorMessage"]}:{ErrorMessage}";
|
||||
string exceptionString = Exception == null ? string.Empty : ErrorMessage == null ? $"{DefaultResource.Localizer["Exception"]}:{Exception}" : $"{Environment.NewLine}{DefaultResource.Localizer["Exception"]}:{Exception}";
|
||||
|
||||
return $"{messageString}{exceptionString}";
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public struct OperResult<T, T2, T3> : IOperResult<T, T2, T3>
|
||||
{
|
||||
/// <summary>
|
||||
/// 异常堆栈
|
||||
/// </summary>
|
||||
#if NET6_0_OR_GREATER
|
||||
[System.Text.Json.Serialization.JsonIgnore]
|
||||
#endif
|
||||
|
||||
[JsonIgnore]
|
||||
public Exception? Exception { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 默认构造,操作结果会是成功
|
||||
/// </summary>
|
||||
public OperResult()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从另一个操作对象中赋值信息
|
||||
/// </summary>
|
||||
public OperResult(IOperResult operResult)
|
||||
{
|
||||
OperCode = operResult.OperCode;
|
||||
ErrorMessage = operResult.ErrorMessage;
|
||||
Exception = operResult.Exception;
|
||||
ErrorCode = operResult.ErrorCode;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 传入错误信息
|
||||
/// </summary>
|
||||
/// <param name="msg"></param>
|
||||
public OperResult(string msg)
|
||||
{
|
||||
OperCode = 500;
|
||||
ErrorMessage = msg;
|
||||
ErrorCode = ErrorCodeEnum.InvokeFail;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 传入异常堆栈
|
||||
/// </summary>
|
||||
public OperResult(Exception ex)
|
||||
{
|
||||
OperCode = 500;
|
||||
Exception = ex;
|
||||
ErrorMessage = ex.Message;
|
||||
//指定Timeout或OperationCanceled为超时取消
|
||||
if (ex is TimeoutException || ex is OperationCanceledException)
|
||||
{
|
||||
ErrorCode = ErrorCodeEnum.Canceled;
|
||||
}
|
||||
else if (ex is ReturnErrorException)
|
||||
{
|
||||
ErrorCode = ErrorCodeEnum.RetuenError;
|
||||
}
|
||||
else
|
||||
{
|
||||
ErrorCode = ErrorCodeEnum.InvokeFail;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 传入错误信息与异常堆栈
|
||||
/// </summary>
|
||||
public OperResult(string msg, Exception ex) : this(ex)
|
||||
{
|
||||
ErrorMessage = msg;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public int? OperCode { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool IsSuccess => OperCode == null || OperCode == 0;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string? ErrorMessage { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
#if NET6_0_OR_GREATER
|
||||
[System.Text.Json.Serialization.JsonConverter(typeof(System.Text.Json.Serialization.JsonStringEnumConverter))]
|
||||
#endif
|
||||
|
||||
[JsonConverter(typeof(StringEnumConverter))]
|
||||
public ErrorCodeEnum? ErrorCode { get; private set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public T Content { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public T2 Content2 { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public T3 Content3 { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 返回错误信息与异常堆栈等信息
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public override string ToString()
|
||||
{
|
||||
string messageString = ErrorMessage == null ? string.Empty : $"{DefaultResource.Localizer["ErrorMessage"]}:{ErrorMessage}";
|
||||
string exceptionString = Exception == null ? string.Empty : ErrorMessage == null ? $"{DefaultResource.Localizer["Exception"]}:{Exception}" : $"{Environment.NewLine}{DefaultResource.Localizer["Exception"]}:{Exception}";
|
||||
|
||||
return $"{messageString}{exceptionString}";
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="IOperResult"/>
|
||||
public struct OperResult : IOperResult
|
||||
{
|
||||
/// <summary>
|
||||
/// 成功结果
|
||||
/// </summary>
|
||||
public static OperResult Success = new OperResult();
|
||||
|
||||
/// <summary>
|
||||
/// 异常堆栈
|
||||
/// </summary>
|
||||
#if NET6_0_OR_GREATER
|
||||
[System.Text.Json.Serialization.JsonIgnore]
|
||||
#endif
|
||||
|
||||
[JsonIgnore]
|
||||
public Exception? Exception { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 从另一个操作对象中赋值信息
|
||||
/// </summary>
|
||||
public OperResult(IOperResult operResult)
|
||||
{
|
||||
OperCode = operResult.OperCode;
|
||||
ErrorMessage = operResult.ErrorMessage;
|
||||
Exception = operResult.Exception;
|
||||
ErrorCode = operResult.ErrorCode;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 传入错误信息
|
||||
/// </summary>
|
||||
/// <param name="msg"></param>
|
||||
public OperResult(string msg)
|
||||
{
|
||||
OperCode = 500;
|
||||
ErrorMessage = msg;
|
||||
ErrorCode = ErrorCodeEnum.InvokeFail;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 传入异常堆栈
|
||||
/// </summary>
|
||||
public OperResult(Exception ex)
|
||||
{
|
||||
OperCode = 500;
|
||||
Exception = ex;
|
||||
ErrorMessage = ex.Message;
|
||||
//指定Timeout或OperationCanceled为超时取消
|
||||
if (ex is TimeoutException || ex is OperationCanceledException)
|
||||
{
|
||||
ErrorCode = ErrorCodeEnum.Canceled;
|
||||
}
|
||||
else if (ex is ReturnErrorException)
|
||||
{
|
||||
ErrorCode = ErrorCodeEnum.RetuenError;
|
||||
}
|
||||
else
|
||||
{
|
||||
ErrorCode = ErrorCodeEnum.InvokeFail;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 传入错误信息与异常堆栈
|
||||
/// </summary>
|
||||
public OperResult(string msg, Exception ex) : this(ex)
|
||||
{
|
||||
ErrorMessage = msg;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public int? OperCode { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool IsSuccess => OperCode == null || OperCode == 0;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string? ErrorMessage { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
#if NET6_0_OR_GREATER
|
||||
[System.Text.Json.Serialization.JsonConverter(typeof(System.Text.Json.Serialization.JsonStringEnumConverter))]
|
||||
#endif
|
||||
|
||||
[JsonConverter(typeof(StringEnumConverter))]
|
||||
public ErrorCodeEnum? ErrorCode { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// 返回一个成功结果,并带有结果值
|
||||
/// </summary>
|
||||
public static OperResult<T> CreateSuccessResult<T>(T content)
|
||||
{
|
||||
return new() { Content = content };
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 返回错误信息与异常堆栈等信息
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public override string ToString()
|
||||
{
|
||||
string messageString = ErrorMessage == null ? string.Empty : $"{DefaultResource.Localizer["ErrorMessage"]}:{ErrorMessage}";
|
||||
string exceptionString = Exception == null ? string.Empty : ErrorMessage == null ? $"{DefaultResource.Localizer["Exception"]}:{Exception}" : $"{Environment.NewLine}{DefaultResource.Localizer["Exception"]}:{Exception}";
|
||||
|
||||
return $"{messageString}{exceptionString}";
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="IOperResult"/>
|
||||
public class OperResultClass : IOperResult
|
||||
{
|
||||
/// <summary>
|
||||
/// 异常堆栈
|
||||
/// </summary>
|
||||
#if NET6_0_OR_GREATER
|
||||
[System.Text.Json.Serialization.JsonIgnore]
|
||||
#endif
|
||||
|
||||
[JsonIgnore]
|
||||
public Exception? Exception { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 从另一个操作对象中赋值信息
|
||||
/// </summary>
|
||||
public OperResultClass(IOperResult operResult)
|
||||
{
|
||||
OperCode = operResult.OperCode;
|
||||
ErrorMessage = operResult.ErrorMessage;
|
||||
Exception = operResult.Exception;
|
||||
ErrorCode = operResult.ErrorCode;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 传入错误信息
|
||||
/// </summary>
|
||||
/// <param name="msg"></param>
|
||||
public OperResultClass(string msg)
|
||||
{
|
||||
OperCode = 500;
|
||||
ErrorMessage = msg;
|
||||
ErrorCode = ErrorCodeEnum.InvokeFail;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 传入异常堆栈
|
||||
/// </summary>
|
||||
public OperResultClass(Exception ex)
|
||||
{
|
||||
OperCode = 500;
|
||||
Exception = ex;
|
||||
ErrorMessage = ex.Message;
|
||||
//指定Timeout或OperationCanceled为超时取消
|
||||
if (ex is TimeoutException || ex is OperationCanceledException)
|
||||
{
|
||||
ErrorCode = ErrorCodeEnum.Canceled;
|
||||
}
|
||||
else if (ex is ReturnErrorException)
|
||||
{
|
||||
ErrorCode = ErrorCodeEnum.RetuenError;
|
||||
}
|
||||
else
|
||||
{
|
||||
ErrorCode = ErrorCodeEnum.InvokeFail;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 传入错误信息与异常堆栈
|
||||
/// </summary>
|
||||
public OperResultClass(string msg, Exception ex) : this(ex)
|
||||
{
|
||||
ErrorMessage = msg;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 默认构造,操作结果会是成功
|
||||
/// </summary>
|
||||
public OperResultClass()
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public int? OperCode { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool IsSuccess => OperCode == null || OperCode == 0;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string? ErrorMessage { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
#if NET6_0_OR_GREATER
|
||||
[System.Text.Json.Serialization.JsonConverter(typeof(System.Text.Json.Serialization.JsonStringEnumConverter))]
|
||||
#endif
|
||||
|
||||
[JsonConverter(typeof(StringEnumConverter))]
|
||||
public ErrorCodeEnum? ErrorCode { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// 返回一个成功结果,并带有结果值
|
||||
/// </summary>
|
||||
public static OperResult<T> CreateSuccessResult<T>(T content)
|
||||
{
|
||||
return new() { Content = content };
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 返回错误信息与异常堆栈等信息
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public override string ToString()
|
||||
{
|
||||
string messageString = ErrorMessage == null ? string.Empty : $"{DefaultResource.Localizer["ErrorMessage"]}:{ErrorMessage}";
|
||||
string exceptionString = Exception == null ? string.Empty : ErrorMessage == null ? $"{DefaultResource.Localizer["Exception"]}:{Exception}" : $"{Environment.NewLine}{DefaultResource.Localizer["Exception"]}:{Exception}";
|
||||
|
||||
return $"{messageString}{exceptionString}";
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public class OperResultClass<T> : OperResultClass, IOperResult<T>
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public OperResultClass() : base()
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public OperResultClass(IOperResult operResult) : base(operResult)
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public OperResultClass(string msg) : base(msg)
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public OperResultClass(Exception ex) : base(ex)
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public OperResultClass(string msg, Exception ex) : base(msg, ex)
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public T Content { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace ThingsGateway.Foundation;
|
||||
|
||||
/// <summary>
|
||||
/// OperResultExtensions
|
||||
/// </summary>
|
||||
public static class OperResultExtension
|
||||
{
|
||||
/// <summary>
|
||||
/// 转换对应类型
|
||||
/// </summary>
|
||||
public static OperResult<TResult> GetResultFromBytes<TResult>(this OperResult<byte[]> result, Func<byte[]?, TResult> translator)
|
||||
{
|
||||
try
|
||||
{
|
||||
return result.IsSuccess ? new() { Content = translator(result.Content) } : new OperResult<TResult>(result);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new OperResult<TResult>(DefaultResource.Localizer["TransBytesError", result.Content?.ToHexString(' '), result.Content?.Length], ex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 操作成功则继续,并返回对应结果值
|
||||
/// </summary>
|
||||
public static OperResult<T1> OperResultFrom<T1>(this OperResult result, Func<T1> func)
|
||||
{
|
||||
if (result.IsSuccess)
|
||||
return new OperResult<T1>() { Content = func() };
|
||||
else
|
||||
return new OperResult<T1>(result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 操作成功则继续,并返回对应结果值
|
||||
/// </summary>
|
||||
public static OperResult<T1> OperResultFrom<T1, T2>(this OperResult<T2> result, Func<T1> func)
|
||||
{
|
||||
if (result.IsSuccess)
|
||||
return new OperResult<T1>() { Content = func() };
|
||||
else
|
||||
return new OperResult<T1>(result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 操作成功则继续
|
||||
/// </summary>
|
||||
public static OperResult Then(this OperResult result, Func<OperResult> func)
|
||||
{
|
||||
return !result.IsSuccess ? result : func();
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="Then(OperResult, Func{OperResult})"/>
|
||||
public static OperResult<T> Then<T>(this OperResult result, Func<OperResult<T>> func)
|
||||
{
|
||||
return !result.IsSuccess ? new OperResult<T>(result) : func();
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="Then(OperResult, Func{OperResult})"/>
|
||||
public static OperResult<T2> Then<T, T2>(this OperResult<T> result, Func<T?, OperResult<T2>> func)
|
||||
{
|
||||
return !result.IsSuccess ? new OperResult<T2>(result) : func(result.Content);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="Then(OperResult, Func{OperResult})"/>
|
||||
public static OperResult<T, T2> Then<T, T2>(this OperResult result, Func<OperResult<T, T2>> func)
|
||||
{
|
||||
return !result.IsSuccess ? new OperResult<T, T2>(result) : func();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace ThingsGateway.Foundation;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public static class OperResultUtil
|
||||
{
|
||||
/// <summary>
|
||||
/// 等待读取到指定值,超时返回错误
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="func">执行回调</param>
|
||||
/// <param name="value">比较值</param>
|
||||
/// <param name="timeout">超时时间</param>
|
||||
/// <param name="cancellationToken">取消令箭</param>
|
||||
/// <returns></returns>
|
||||
public static async ValueTask<OperResult> WaitAsync<T>(Func<CancellationToken, ValueTask<OperResult<T>>> func, T value, int timeout, CancellationToken cancellationToken = default) where T : IEquatable<T>
|
||||
{
|
||||
using var timeoutToken = new CancellationTokenSource(TimeSpan.FromMilliseconds(timeout));
|
||||
using CancellationTokenSource stoppingToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, timeoutToken.Token);
|
||||
|
||||
while (!stoppingToken.IsCancellationRequested)
|
||||
{
|
||||
var result = await func(stoppingToken.Token).ConfigureAwait(false);
|
||||
if (result.IsSuccess)
|
||||
{
|
||||
if (result.Content.Equals(value))
|
||||
{
|
||||
return OperResult.Success;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return result;
|
||||
}
|
||||
}
|
||||
return new OperResult("Timeout");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace ThingsGateway.Foundation;
|
||||
|
||||
/// <summary>
|
||||
/// 设备返回错误
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class ReturnErrorException : Exception
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public ReturnErrorException()
|
||||
{ }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ReturnErrorException(string message) : base(message) { }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ReturnErrorException(string message, System.Exception inner) : base(message, inner) { }
|
||||
}
|
||||
493
src/Foundation/ThingsGateway.Foundation/Protocol/IProtocol.cs
Normal file
493
src/Foundation/ThingsGateway.Foundation/Protocol/IProtocol.cs
Normal file
@@ -0,0 +1,493 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace ThingsGateway.Foundation;
|
||||
|
||||
/// <summary>
|
||||
/// 协议接口
|
||||
/// </summary>
|
||||
public interface IProtocol : IDisposable
|
||||
{
|
||||
#region 属性
|
||||
|
||||
/// <summary>
|
||||
/// 组包缓存时间
|
||||
/// </summary>
|
||||
int CacheTimeout { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 通道
|
||||
/// </summary>
|
||||
IChannel Channel { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 连接超时时间
|
||||
/// </summary>
|
||||
ushort ConnectTimeout { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 数据解析规则
|
||||
/// </summary>
|
||||
DataFormatEnum DataFormat { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否需要并发锁,默认为true,对于工业主从协议,通常是必须的
|
||||
/// </summary>
|
||||
bool IsSingleThread { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 日志
|
||||
/// </summary>
|
||||
ILog? Logger { get; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
bool OnLine { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 一个寄存器所占的字节长度
|
||||
/// </summary>
|
||||
int RegisterByteLength { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 发送前延时
|
||||
/// </summary>
|
||||
int SendDelayTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 数据解析规则
|
||||
/// </summary>
|
||||
IThingsGatewayBitConverter ThingsGatewayBitConverter { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 读写超时时间
|
||||
/// </summary>
|
||||
int Timeout { get; set; }
|
||||
|
||||
#endregion 属性
|
||||
|
||||
#region 适配器
|
||||
|
||||
/// <summary>
|
||||
/// 获取新的适配器实例
|
||||
/// </summary>
|
||||
DataHandlingAdapter GetDataAdapter();
|
||||
|
||||
#endregion 适配器
|
||||
|
||||
#region 变量地址解析
|
||||
|
||||
/// <summary>
|
||||
/// 寄存器地址的详细说明
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
string GetAddressDescription();
|
||||
/// <summary>
|
||||
/// 获取变量地址对应的bit偏移,默认0
|
||||
/// </summary>
|
||||
/// <param name="address">变量地址</param>
|
||||
/// <returns></returns>
|
||||
int GetBitOffsetDefault(string address);
|
||||
/// <summary>
|
||||
/// 获取变量地址对应的bit偏移
|
||||
/// </summary>
|
||||
/// <param name="address">变量地址</param>
|
||||
/// <returns></returns>
|
||||
int? GetBitOffset(string address);
|
||||
|
||||
/// <summary>
|
||||
/// 获取数据类型对应的寄存器长度
|
||||
/// </summary>
|
||||
/// <param name="address">寄存器地址</param>
|
||||
/// <param name="length">读取数量</param>
|
||||
/// <param name="typeLength">读取数据类型对应的字节长度</param>
|
||||
/// <param name="isBool">是否按布尔解析</param>
|
||||
/// <returns></returns>
|
||||
int GetLength(string address, int length, int typeLength, bool isBool = false);
|
||||
|
||||
/// <summary>
|
||||
/// 连读寄存器打包
|
||||
/// </summary>
|
||||
List<T> LoadSourceRead<T>(IEnumerable<IVariable> deviceVariables, int maxPack, int defaultIntervalTime) where T : IVariableSource, new();
|
||||
|
||||
#endregion 变量地址解析
|
||||
|
||||
#region 动态类型读写
|
||||
|
||||
/// <summary>
|
||||
/// 通过数据类型,获取对应的类型值
|
||||
/// </summary>
|
||||
/// <param name="address">变量地址</param>
|
||||
/// <param name="length">读取长度</param>
|
||||
/// <param name="dataType">数据类型</param>
|
||||
/// <param name="cancellationToken">取消令箭</param>
|
||||
/// <returns></returns>
|
||||
ValueTask<IOperResult<Array>> ReadAsync(string address, int length, DataTypeEnum dataType, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// 根据数据类型,写入类型值
|
||||
/// </summary>
|
||||
/// <param name="address">变量地址</param>
|
||||
/// <param name="value">值</param>
|
||||
/// <param name="dataType">数据类型</param>
|
||||
/// <param name="cancellationToken">取消令箭</param>
|
||||
/// <returns></returns>
|
||||
ValueTask<OperResult> WriteAsync(string address, JToken value, DataTypeEnum dataType, CancellationToken cancellationToken = default);
|
||||
|
||||
#endregion 动态类型读写
|
||||
|
||||
#region 读取
|
||||
|
||||
/// <summary>
|
||||
/// 批量读取字节数组信息,需要指定地址和长度
|
||||
/// </summary>
|
||||
/// <param name="address">变量地址</param>
|
||||
/// <param name="length">读取寄存器数量,对于不同PLC,对应的字节数量可能不一样</param>
|
||||
/// <param name="cancellationToken">取消令箭</param>
|
||||
/// <returns></returns>
|
||||
ValueTask<OperResult<byte[]>> ReadAsync(string address, int length, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// 读取布尔量数组
|
||||
/// </summary>
|
||||
/// <param name="address">变量地址</param>
|
||||
/// <param name="length">读取长度</param>
|
||||
/// <param name="bitConverter">转换规则</param>
|
||||
/// <param name="cancellationToken">取消令箭</param>
|
||||
/// <returns></returns>
|
||||
ValueTask<OperResult<bool[]>> ReadBooleanAsync(string address, int length, IThingsGatewayBitConverter bitConverter = null, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// 读取Double数组
|
||||
/// </summary>
|
||||
/// <param name="address">变量地址</param>
|
||||
/// <param name="length">读取长度</param>
|
||||
/// <param name="bitConverter">转换规则</param>
|
||||
/// <param name="cancellationToken">取消令箭</param>
|
||||
/// <returns></returns>
|
||||
ValueTask<OperResult<double[]>> ReadDoubleAsync(string address, int length, IThingsGatewayBitConverter bitConverter = null, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// 读取Int16数组
|
||||
/// </summary>
|
||||
/// <param name="address">变量地址</param>
|
||||
/// <param name="length">读取长度</param>
|
||||
/// <param name="bitConverter">转换规则</param>
|
||||
/// <param name="cancellationToken">取消令箭</param>
|
||||
/// <returns></returns>
|
||||
ValueTask<OperResult<short[]>> ReadInt16Async(string address, int length, IThingsGatewayBitConverter bitConverter = null, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// 读取Int32数组
|
||||
/// </summary>
|
||||
/// <param name="address">变量地址</param>
|
||||
/// <param name="length">读取长度</param>
|
||||
/// <param name="bitConverter">转换规则</param>
|
||||
/// <param name="cancellationToken">取消令箭</param>
|
||||
/// <returns></returns>
|
||||
ValueTask<OperResult<int[]>> ReadInt32Async(string address, int length, IThingsGatewayBitConverter bitConverter = null, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// 读取Int64数组
|
||||
/// </summary>
|
||||
/// <param name="address">变量地址</param>
|
||||
/// <param name="length">读取长度</param>
|
||||
/// <param name="bitConverter">转换规则</param>
|
||||
/// <param name="cancellationToken">取消令箭</param>
|
||||
/// <returns></returns>
|
||||
ValueTask<OperResult<long[]>> ReadInt64Async(string address, int length, IThingsGatewayBitConverter bitConverter = null, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// 读取Single数组
|
||||
/// </summary>
|
||||
/// <param name="address">变量地址</param>
|
||||
/// <param name="length">读取长度</param>
|
||||
/// <param name="bitConverter">转换规则</param>
|
||||
/// <param name="cancellationToken">取消令箭</param>
|
||||
/// <returns></returns>
|
||||
ValueTask<OperResult<float[]>> ReadSingleAsync(string address, int length, IThingsGatewayBitConverter bitConverter = null, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// 读取String
|
||||
/// </summary>
|
||||
/// <param name="address">变量地址</param>
|
||||
/// <param name="length">读取长度</param>
|
||||
/// <param name="bitConverter">转换规则</param>
|
||||
/// <param name="cancellationToken">取消令箭</param>
|
||||
/// <returns></returns>
|
||||
ValueTask<OperResult<string[]>> ReadStringAsync(string address, int length, IThingsGatewayBitConverter bitConverter = null, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// 读取UInt16数组
|
||||
/// </summary>
|
||||
/// <param name="address">变量地址</param>
|
||||
/// <param name="length">读取长度</param>
|
||||
/// <param name="bitConverter">转换规则</param>
|
||||
/// <param name="cancellationToken">取消令箭</param>
|
||||
/// <returns></returns>
|
||||
ValueTask<OperResult<ushort[]>> ReadUInt16Async(string address, int length, IThingsGatewayBitConverter bitConverter = null, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// 读取UInt32数组
|
||||
/// </summary>
|
||||
/// <param name="address">变量地址</param>
|
||||
/// <param name="length">读取长度</param>
|
||||
/// <param name="bitConverter">转换规则</param>
|
||||
/// <param name="cancellationToken">取消令箭</param>
|
||||
/// <returns></returns>
|
||||
ValueTask<OperResult<uint[]>> ReadUInt32Async(string address, int length, IThingsGatewayBitConverter bitConverter = null, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// 读取UInt32数组
|
||||
/// </summary>
|
||||
/// <param name="address">变量地址</param>
|
||||
/// <param name="length">读取长度</param>
|
||||
/// <param name="bitConverter">转换规则</param>
|
||||
/// <param name="cancellationToken">取消令箭</param>
|
||||
/// <returns></returns>
|
||||
ValueTask<OperResult<ulong[]>> ReadUInt64Async(string address, int length, IThingsGatewayBitConverter bitConverter = null, CancellationToken cancellationToken = default);
|
||||
|
||||
#endregion 读取
|
||||
|
||||
#region 写入
|
||||
|
||||
/// <summary>
|
||||
/// 写入原始的byte数组数据到指定的地址,返回结果
|
||||
/// </summary>
|
||||
ValueTask<OperResult> WriteAsync(string address, byte[] value, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// 写入bool数组数据,返回结果
|
||||
/// </summary>
|
||||
ValueTask<OperResult> WriteAsync(string address, bool[] value, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// 写入bool数据,返回结果
|
||||
/// </summary>
|
||||
ValueTask<OperResult> WriteAsync(string address, bool value, IThingsGatewayBitConverter bitConverter = null, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// 写入byte数据,返回结果
|
||||
/// </summary>
|
||||
ValueTask<OperResult> WriteAsync(string address, byte value, IThingsGatewayBitConverter bitConverter = null, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// 写入short数据,返回结果
|
||||
/// </summary>
|
||||
ValueTask<OperResult> WriteAsync(string address, short value, IThingsGatewayBitConverter bitConverter = null, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// 写入ushort数据,返回结果
|
||||
/// </summary>
|
||||
ValueTask<OperResult> WriteAsync(string address, ushort value, IThingsGatewayBitConverter bitConverter = null, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// 写入int数据,返回结果
|
||||
/// </summary>
|
||||
ValueTask<OperResult> WriteAsync(string address, int value, IThingsGatewayBitConverter bitConverter = null, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// 写入uint数据,返回结果
|
||||
/// </summary>
|
||||
ValueTask<OperResult> WriteAsync(string address, uint value, IThingsGatewayBitConverter bitConverter = null, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// 写入long数据,返回结果
|
||||
/// </summary>
|
||||
ValueTask<OperResult> WriteAsync(string address, long value, IThingsGatewayBitConverter bitConverter = null, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// 写入ulong数据,返回结果
|
||||
/// </summary>
|
||||
ValueTask<OperResult> WriteAsync(string address, ulong value, IThingsGatewayBitConverter bitConverter = null, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// 写入float数据,返回结果
|
||||
/// </summary>
|
||||
ValueTask<OperResult> WriteAsync(string address, float value, IThingsGatewayBitConverter bitConverter = null, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// 写入double数据,返回结果
|
||||
/// </summary>
|
||||
ValueTask<OperResult> WriteAsync(string address, double value, IThingsGatewayBitConverter bitConverter = null, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// 写入string数据,返回结果
|
||||
/// </summary>
|
||||
ValueTask<OperResult> WriteAsync(string address, string value, IThingsGatewayBitConverter bitConverter = null, CancellationToken cancellationToken = default);
|
||||
|
||||
#endregion 写入
|
||||
|
||||
#region 写入数组
|
||||
|
||||
/// <summary>
|
||||
/// 写入String数组
|
||||
/// </summary>
|
||||
/// <param name="address">变量地址</param>
|
||||
/// <param name="value">值</param>
|
||||
/// <param name="bitConverter">转换规则</param>
|
||||
/// <param name="cancellationToken">取消令箭</param>
|
||||
/// <returns>写入结果</returns>
|
||||
ValueTask<OperResult> WriteAsync(string address, string[] value, IThingsGatewayBitConverter bitConverter = null, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// 写入Double数组
|
||||
/// </summary>
|
||||
/// <param name="address">变量地址</param>
|
||||
/// <param name="value">值</param>
|
||||
/// <param name="bitConverter">转换规则</param>
|
||||
/// <param name="cancellationToken">取消令箭</param>
|
||||
/// <returns>写入结果</returns>
|
||||
ValueTask<OperResult> WriteAsync(string address, double[] value, IThingsGatewayBitConverter bitConverter = null, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// 写入Single数组
|
||||
/// </summary>
|
||||
/// <param name="address">变量地址</param>
|
||||
/// <param name="value">值</param>
|
||||
/// <param name="bitConverter">转换规则</param>
|
||||
/// <param name="cancellationToken">取消令箭</param>
|
||||
/// <returns>写入结果</returns>
|
||||
ValueTask<OperResult> WriteAsync(string address, float[] value, IThingsGatewayBitConverter bitConverter = null, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// 写入Int32数组
|
||||
/// </summary>
|
||||
/// <param name="address">变量地址</param>
|
||||
/// <param name="value">值</param>
|
||||
/// <param name="bitConverter">转换规则</param>
|
||||
/// <param name="cancellationToken">取消令箭</param>
|
||||
/// <returns>写入结果</returns>
|
||||
ValueTask<OperResult> WriteAsync(string address, int[] value, IThingsGatewayBitConverter bitConverter = null, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// 写入Int64数组
|
||||
/// </summary>
|
||||
/// <param name="address">变量地址</param>
|
||||
/// <param name="value">值</param>
|
||||
/// <param name="bitConverter">转换规则</param>
|
||||
/// <param name="cancellationToken">取消令箭</param>
|
||||
/// <returns>写入结果</returns>
|
||||
ValueTask<OperResult> WriteAsync(string address, long[] value, IThingsGatewayBitConverter bitConverter = null, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// 写入Int16数组
|
||||
/// </summary>
|
||||
/// <param name="address">变量地址</param>
|
||||
/// <param name="value">值</param>
|
||||
/// <param name="bitConverter">转换规则</param>
|
||||
/// <param name="cancellationToken">取消令箭</param>
|
||||
/// <returns>写入结果</returns>
|
||||
ValueTask<OperResult> WriteAsync(string address, short[] value, IThingsGatewayBitConverter bitConverter = null, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// 写入UInt32数组
|
||||
/// </summary>
|
||||
/// <param name="address">变量地址</param>
|
||||
/// <param name="value">值</param>
|
||||
/// <param name="bitConverter">转换规则</param>
|
||||
/// <param name="cancellationToken">取消令箭</param>
|
||||
/// <returns>写入结果</returns>
|
||||
ValueTask<OperResult> WriteAsync(string address, uint[] value, IThingsGatewayBitConverter bitConverter = null, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// 写入UInt64数组
|
||||
/// </summary>
|
||||
/// <param name="address">变量地址</param>
|
||||
/// <param name="value">值</param>
|
||||
/// <param name="bitConverter">转换规则</param>
|
||||
/// <param name="cancellationToken">取消令箭</param>
|
||||
/// <returns>写入结果</returns>
|
||||
ValueTask<OperResult> WriteAsync(string address, ulong[] value, IThingsGatewayBitConverter bitConverter = null, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// 写入UInt16数组
|
||||
/// </summary>
|
||||
/// <param name="address">变量地址</param>
|
||||
/// <param name="value">值</param>
|
||||
/// <param name="bitConverter">转换规则</param>
|
||||
/// <param name="cancellationToken">取消令箭</param>
|
||||
/// <returns>写入结果</returns>
|
||||
ValueTask<OperResult> WriteAsync(string address, ushort[] value, IThingsGatewayBitConverter bitConverter = null, CancellationToken cancellationToken = default);
|
||||
|
||||
#endregion 写入数组
|
||||
|
||||
/// <summary>
|
||||
/// 布尔量解析时是否需要按字反转
|
||||
/// </summary>
|
||||
/// <param name="address">变量地址</param>
|
||||
/// <returns></returns>
|
||||
bool BitReverse(string address);
|
||||
|
||||
/// <summary>
|
||||
/// 断开连接
|
||||
/// </summary>
|
||||
/// <param name="msg"></param>
|
||||
/// <returns></returns>
|
||||
Task CloseAsync(string msg = null);
|
||||
|
||||
/// <summary>
|
||||
/// 配置IPluginManager
|
||||
/// </summary>
|
||||
Action<IPluginManager> ConfigurePlugins();
|
||||
|
||||
/// <summary>
|
||||
/// 连接
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns></returns>
|
||||
Task ConnectAsync(CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// 获取通道
|
||||
/// </summary>
|
||||
/// <param name="socketId"></param>
|
||||
/// <returns></returns>
|
||||
ValueTask<OperResult<IClientChannel>> GetChannelAsync(string socketId);
|
||||
|
||||
/// <summary>
|
||||
/// 发送,会经过适配器,可传入<see cref="IClientChannel"/>,如果为空,则默认通道必须为<see cref="IClientChannel"/>类型
|
||||
/// </summary>
|
||||
/// <param name="sendMessage">发送字节数组</param>
|
||||
/// <param name="token">取消令箭</param>
|
||||
/// <param name="channel">通道</param>
|
||||
/// <returns>返回消息体</returns>
|
||||
ValueTask<OperResult> SendAsync(ISendMessage sendMessage, IClientChannel channel = default, CancellationToken token = default);
|
||||
|
||||
/// <summary>
|
||||
/// 发送,会经过适配器,可传入socketId,如果为空,则默认通道必须为<see cref="IClientChannel"/>类型
|
||||
/// </summary>
|
||||
/// <param name="socketId">通道</param>
|
||||
/// <param name="sendMessage">发送字节数组</param>
|
||||
/// <param name="cancellationToken">取消令箭</param>
|
||||
/// <returns>返回消息体</returns>
|
||||
ValueTask<OperResult> SendAsync(ISendMessage sendMessage, string socketId, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// 发送并等待返回,会经过适配器,可传入<see cref="IClientChannel"/>,如果为空,则默认通道必须为<see cref="IClientChannel"/>类型
|
||||
/// </summary>
|
||||
/// <param name="command">发送字节数组</param>
|
||||
/// <param name="waitData">waitData</param>
|
||||
/// <param name="cancellationToken">取消令箭</param>
|
||||
/// <param name="channel">通道</param>
|
||||
/// <returns>返回消息体</returns>
|
||||
ValueTask<OperResult<byte[]>> SendThenReturnAsync(ISendMessage command, IClientChannel channel = default, WaitDataAsync<MessageBase> waitData = default, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// 发送并等待返回,会经过适配器,可传入socketId,如果为空,则默认通道必须为<see cref="IClientChannel"/>类型
|
||||
/// </summary>
|
||||
/// <param name="socketId">通道</param>
|
||||
/// <param name="sendMessage">发送字节数组</param>
|
||||
/// <param name="waitData">waitData</param>
|
||||
/// <param name="cancellationToken">取消令箭</param>
|
||||
/// <returns>返回消息体</returns>
|
||||
ValueTask<OperResult<byte[]>> SendThenReturnAsync(ISendMessage sendMessage, string socketId, WaitDataAsync<MessageBase> waitData = default, CancellationToken cancellationToken = default);
|
||||
}
|
||||
859
src/Foundation/ThingsGateway.Foundation/Protocol/ProtocolBase.cs
Normal file
859
src/Foundation/ThingsGateway.Foundation/Protocol/ProtocolBase.cs
Normal file
@@ -0,0 +1,859 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
using ThingsGateway.Foundation.Extension.Generic;
|
||||
using ThingsGateway.Foundation.Extension.String;
|
||||
using ThingsGateway.NewLife.Extension;
|
||||
|
||||
using TouchSocket.Resources;
|
||||
|
||||
namespace ThingsGateway.Foundation;
|
||||
|
||||
/// <summary>
|
||||
/// 协议基类
|
||||
/// </summary>
|
||||
public abstract class ProtocolBase : DisposableObject, IProtocol
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public ProtocolBase(IChannel channel)
|
||||
{
|
||||
if (channel == null) throw new ArgumentNullException(nameof(channel));
|
||||
lock (channel)
|
||||
{
|
||||
channel.Collects.Add(this);
|
||||
Channel = channel;
|
||||
Logger = channel.Logger;
|
||||
Channel.Starting.Add(ChannelStarting);
|
||||
Channel.Stoped.Add(ChannelStoped);
|
||||
Channel.Stoping.Add(ChannelStoping);
|
||||
Channel.Started.Add(ChannelStarted);
|
||||
Channel.ChannelReceived.Add(ChannelReceived);
|
||||
Channel.Config.ConfigurePlugins(ConfigurePlugins());
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
~ProtocolBase()
|
||||
{
|
||||
this.SafeDispose();
|
||||
}
|
||||
|
||||
#region 属性
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual int CacheTimeout { get; set; } = 1000;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual int SendDelayTime { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual int Timeout { get; set; } = 3000;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual ushort ConnectTimeout { get; set; } = 3000;
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc cref="IThingsGatewayBitConverter.IsStringReverseByteWord"/>
|
||||
/// </summary>
|
||||
public bool IsStringReverseByteWord
|
||||
{
|
||||
get
|
||||
{
|
||||
return ThingsGatewayBitConverter.IsStringReverseByteWord;
|
||||
}
|
||||
set
|
||||
{
|
||||
ThingsGatewayBitConverter.IsStringReverseByteWord = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual DataFormatEnum DataFormat
|
||||
{
|
||||
get => ThingsGatewayBitConverter.DataFormat;
|
||||
set => ThingsGatewayBitConverter.DataFormat = value;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual IChannel Channel { get; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual ILog? Logger { get; protected set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual int RegisterByteLength { get; protected set; } = 1;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual IThingsGatewayBitConverter ThingsGatewayBitConverter { get; protected set; } = new ThingsGatewayBitConverter();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool OnLine => Channel.Online;
|
||||
|
||||
#endregion 属性
|
||||
|
||||
#region 适配器
|
||||
|
||||
/// <inheritdoc/>
|
||||
public abstract DataHandlingAdapter GetDataAdapter();
|
||||
|
||||
/// <summary>
|
||||
/// 通道连接成功时,如果通道存在其他设备并且不希望其他设备处理时,返回true
|
||||
/// </summary>
|
||||
/// <param name="channel"></param>
|
||||
/// <returns></returns>
|
||||
protected virtual Task<bool> ChannelStarted(IClientChannel channel)
|
||||
{
|
||||
return Task.FromResult(false);
|
||||
}
|
||||
/// <summary>
|
||||
/// 通道断开连接前,如果通道存在其他设备并且不希望其他设备处理时,返回null
|
||||
/// </summary>
|
||||
/// <param name="channel"></param>
|
||||
/// <returns></returns>
|
||||
protected virtual Task<bool> ChannelStoping(IClientChannel channel)
|
||||
{
|
||||
return Task.FromResult(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 通道断开连接后,如果通道存在其他设备并且不希望其他设备处理时,返回null
|
||||
/// </summary>
|
||||
/// <param name="channel"></param>
|
||||
/// <returns></returns>
|
||||
protected Task<bool> ChannelStoped(IClientChannel channel)
|
||||
{
|
||||
try
|
||||
{
|
||||
channel.WaitHandlePool.CancelAll();
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
|
||||
return Task.FromResult(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 通道即将连接成功时,会设置适配器,如果通道存在其他设备并且不希望其他设备处理时,返回null
|
||||
/// </summary>
|
||||
/// <param name="channel"></param>
|
||||
/// <returns></returns>
|
||||
protected virtual Task<bool> ChannelStarting(IClientChannel channel)
|
||||
{
|
||||
channel.SetDataHandlingAdapter(GetDataAdapter());
|
||||
return Task.FromResult(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检测通道是否存在其他设备,如果有的话会重新设置,没有则无任何操作
|
||||
/// </summary>
|
||||
protected virtual void SetDataAdapter()
|
||||
{
|
||||
if (Channel.Collects.Count > 1)
|
||||
{
|
||||
if (Channel is IClientChannel clientChannel)
|
||||
{
|
||||
var dataHandlingAdapter = GetDataAdapter();
|
||||
if (dataHandlingAdapter.GetType() != clientChannel.ReadOnlyDataHandlingAdapter?.GetType())
|
||||
clientChannel.SetDataHandlingAdapter(dataHandlingAdapter);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion 适配器
|
||||
|
||||
#region 变量地址解析
|
||||
|
||||
/// <inheritdoc/>
|
||||
public abstract List<T> LoadSourceRead<T>(IEnumerable<IVariable> deviceVariables, int maxPack, int defaultIntervalTime) where T : IVariableSource, new();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual string GetAddressDescription()
|
||||
{
|
||||
return DefaultResource.Localizer["DefaultAddressDes"];
|
||||
}
|
||||
/// <summary>
|
||||
/// 获取bit偏移量
|
||||
/// </summary>
|
||||
/// <param name="address"></param>
|
||||
/// <returns></returns>
|
||||
public int GetBitOffsetDefault(string address)
|
||||
{
|
||||
return GetBitOffset(address) ?? 0;
|
||||
}
|
||||
/// <summary>
|
||||
/// 获取bit偏移量
|
||||
/// </summary>
|
||||
/// <param name="address"></param>
|
||||
/// <returns></returns>
|
||||
public virtual int? GetBitOffset(string address)
|
||||
{
|
||||
int? bitIndex = null;
|
||||
if (address?.IndexOf('.') > 0)
|
||||
bitIndex = address.SplitStringByDelimiter().Last().ToInt();
|
||||
return bitIndex;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual bool BitReverse(string address)
|
||||
{
|
||||
return address?.IndexOf('.') > 0;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual int GetLength(string address, int length, int typeLength, bool isBool = false)
|
||||
{
|
||||
var result = Math.Ceiling((double)length * typeLength / RegisterByteLength);
|
||||
if (isBool && GetBitOffset(address) != null)
|
||||
{
|
||||
var data = Math.Ceiling((double)length / RegisterByteLength / 8);
|
||||
return (int)data;
|
||||
}
|
||||
else
|
||||
{
|
||||
return (int)result;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion 变量地址解析
|
||||
|
||||
#region 设备异步返回
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Task ConnectAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
return Channel.ConnectAsync(ConnectTimeout, cancellationToken);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Task CloseAsync(string msg = default)
|
||||
{
|
||||
return Channel.CloseAsync(msg);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 接收,非主动发送的情况,重写实现非主从并发通讯协议,如果通道存在其他设备并且不希望其他设备处理时,设置<see cref="TouchSocketEventArgs.Handled"/> 为true
|
||||
/// </summary>
|
||||
/// <param name="client"></param>
|
||||
/// <param name="e"></param>
|
||||
/// <returns></returns>
|
||||
protected virtual Task ChannelReceived(IClientChannel client, ReceivedDataEventArgs e)
|
||||
{
|
||||
|
||||
if (e.RequestInfo is MessageBase response)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!client.WaitHandlePool.SetRun(response))
|
||||
{
|
||||
//非主动发送的情况,重写实现非主从并发通讯协议
|
||||
}
|
||||
else
|
||||
{
|
||||
e.Handled = true;
|
||||
//Logger?.LogTrace($"MessageBase.Sign : {response.Sign}");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger?.LogWarning(ex, $"Response {response.Sign}");
|
||||
}
|
||||
}
|
||||
|
||||
return EasyTask.CompletedTask;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual async ValueTask<OperResult> SendAsync(ISendMessage sendMessage, IClientChannel channel = default, CancellationToken token = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!Channel.Online)
|
||||
await Channel.ConnectAsync(ConnectTimeout, token).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await Task.Delay(200, token).ConfigureAwait(false);
|
||||
return new(ex);
|
||||
}
|
||||
|
||||
if (SendDelayTime != 0)
|
||||
await Task.Delay(SendDelayTime, token).ConfigureAwait(false);
|
||||
|
||||
if (token.IsCancellationRequested)
|
||||
return new(new OperationCanceledException());
|
||||
|
||||
|
||||
if (channel == default)
|
||||
{
|
||||
if (Channel is not IClientChannel clientChannel) { throw new ArgumentNullException(nameof(channel)); }
|
||||
try
|
||||
{
|
||||
if (IsSingleThread)
|
||||
await clientChannel.WaitLock.WaitAsync(token).ConfigureAwait(false);
|
||||
await clientChannel.SendAsync(sendMessage).ConfigureAwait(false);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (IsSingleThread)
|
||||
clientChannel.WaitLock.Release();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
if (IsSingleThread)
|
||||
await channel.WaitLock.WaitAsync(token).ConfigureAwait(false);
|
||||
await channel.SendAsync(sendMessage).ConfigureAwait(false);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (IsSingleThread)
|
||||
channel.WaitLock.Release();
|
||||
}
|
||||
}
|
||||
return OperResult.Success;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new(ex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual async ValueTask<OperResult> SendAsync(ISendMessage sendMessage, string socketId, CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
var channelResult = await GetChannelAsync(socketId).ConfigureAwait(false);
|
||||
if (!channelResult.IsSuccess) return new OperResult<byte[]>(channelResult);
|
||||
|
||||
return await SendAsync(sendMessage, channelResult.Content, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new(ex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual async ValueTask<OperResult<IClientChannel>> GetChannelAsync(string socketId)
|
||||
{
|
||||
if (Channel.ChannelType == ChannelTypeEnum.TcpService)
|
||||
{
|
||||
if (((TcpServiceChannel)Channel).TryGetClient($"ID={socketId}", out TcpSessionClientChannel? client))
|
||||
{
|
||||
return new OperResult<IClientChannel>() { Content = client };
|
||||
}
|
||||
else
|
||||
{
|
||||
await Task.Delay(1000).ConfigureAwait(false);
|
||||
return (new OperResult<IClientChannel>(DefaultResource.Localizer["DtuNoConnectedWaining", socketId]));
|
||||
}
|
||||
}
|
||||
else
|
||||
return new OperResult<IClientChannel>() { Content = (IClientChannel)Channel };
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual async ValueTask<OperResult<byte[]>> SendThenReturnAsync(ISendMessage sendMessage, string socketId, WaitDataAsync<MessageBase> waitData = default, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var channelResult = await GetChannelAsync(socketId).ConfigureAwait(false);
|
||||
if (!channelResult.IsSuccess) return new OperResult<byte[]>(channelResult);
|
||||
return await SendThenReturnAsync(sendMessage, channelResult.Content, waitData, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual async ValueTask<OperResult<byte[]>> SendThenReturnAsync(ISendMessage command, IClientChannel channel = default, WaitDataAsync<MessageBase> waitData = default, CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!Channel.Online)
|
||||
await Channel.ConnectAsync(ConnectTimeout, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await Task.Delay(200, cancellationToken).ConfigureAwait(false);
|
||||
return new(ex);
|
||||
}
|
||||
if (SendDelayTime != 0)
|
||||
await Task.Delay(SendDelayTime, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
MessageBase? result;
|
||||
|
||||
if (channel == default)
|
||||
{
|
||||
if (Channel is not IClientChannel clientChannel) { throw new ArgumentNullException(nameof(channel)); }
|
||||
if (waitData == default)
|
||||
{
|
||||
waitData = clientChannel.WaitHandlePool.GetWaitDataAsync(out var sign);
|
||||
command.Sign = sign;
|
||||
}
|
||||
result = await GetResponsedDataAsync(command, clientChannel, waitData, Timeout, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (waitData == default)
|
||||
{
|
||||
waitData = channel.WaitHandlePool.GetWaitDataAsync(out var sign);
|
||||
command.Sign = sign;
|
||||
}
|
||||
result = await GetResponsedDataAsync(command, channel, waitData, Timeout, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
return new OperResult<byte[]>(result) { Content = result.Content };
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new(ex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual bool IsSingleThread { get; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// 发送并等待数据
|
||||
/// </summary>
|
||||
protected virtual async ValueTask<MessageBase> GetResponsedDataAsync(ISendMessage command, IClientChannel clientChannel, WaitDataAsync<MessageBase> waitData = default, int timeout = 3000, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (IsSingleThread)
|
||||
await clientChannel.WaitLock.WaitAsync(cancellationToken).ConfigureAwait(false);
|
||||
if (waitData == default)
|
||||
{
|
||||
waitData = clientChannel.WaitHandlePool.GetWaitDataAsync(out var sign);
|
||||
command.Sign = sign;
|
||||
}
|
||||
try
|
||||
{
|
||||
SetDataAdapter();
|
||||
waitData.SetCancellationToken(cancellationToken);
|
||||
|
||||
//Logger?.LogTrace($"Command.Sign : {command.Sign}");
|
||||
|
||||
await clientChannel.SendAsync(command).ConfigureAwait(false);
|
||||
var waitDataStatus = await waitData.WaitAsync(timeout).ConfigureAwait(false);
|
||||
var result = waitDataStatus.Check();
|
||||
if (result.IsSuccess)
|
||||
{
|
||||
var response = waitData.WaitResult;
|
||||
return response;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw result.Exception ?? new(TouchSocketCoreResource.UnknownError);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
clientChannel.WaitHandlePool.Destroy(waitData);
|
||||
if (IsSingleThread)
|
||||
clientChannel.WaitLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion 设备异步返回
|
||||
|
||||
#region 动态类型读写
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual async ValueTask<IOperResult<Array>> ReadAsync(string address, int length, DataTypeEnum dataType, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return dataType switch
|
||||
{
|
||||
DataTypeEnum.String => await ReadStringAsync(address, length, cancellationToken: cancellationToken).ConfigureAwait(false),
|
||||
DataTypeEnum.Boolean => await ReadBooleanAsync(address, length, cancellationToken: cancellationToken).ConfigureAwait(false),
|
||||
DataTypeEnum.Byte => await ReadAsync(address, length, cancellationToken).ConfigureAwait(false),
|
||||
DataTypeEnum.Int16 => await ReadInt16Async(address, length, cancellationToken: cancellationToken).ConfigureAwait(false),
|
||||
DataTypeEnum.UInt16 => await ReadUInt16Async(address, length, cancellationToken: cancellationToken).ConfigureAwait(false),
|
||||
DataTypeEnum.Int32 => await ReadInt32Async(address, length, cancellationToken: cancellationToken).ConfigureAwait(false),
|
||||
DataTypeEnum.UInt32 => await ReadUInt32Async(address, length, cancellationToken: cancellationToken).ConfigureAwait(false),
|
||||
DataTypeEnum.Int64 => await ReadInt64Async(address, length, cancellationToken: cancellationToken).ConfigureAwait(false),
|
||||
DataTypeEnum.UInt64 => await ReadUInt64Async(address, length, cancellationToken: cancellationToken).ConfigureAwait(false),
|
||||
DataTypeEnum.Single => await ReadSingleAsync(address, length, cancellationToken: cancellationToken).ConfigureAwait(false),
|
||||
DataTypeEnum.Double => await ReadDoubleAsync(address, length, cancellationToken: cancellationToken).ConfigureAwait(false),
|
||||
_ => new OperResult<Array>(DefaultResource.Localizer["DataTypeNotSupported", dataType]),
|
||||
};
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual async ValueTask<OperResult> WriteAsync(string address, JToken value, DataTypeEnum dataType, CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (value is JArray jArray)
|
||||
{
|
||||
return dataType switch
|
||||
{
|
||||
DataTypeEnum.String => await WriteAsync(address, jArray.ToObject<String[]>(), cancellationToken: cancellationToken).ConfigureAwait(false),
|
||||
DataTypeEnum.Boolean => await WriteAsync(address, jArray.ToObject<Boolean[]>(), cancellationToken).ConfigureAwait(false),
|
||||
DataTypeEnum.Byte => await WriteAsync(address, jArray.ToObject<Byte[]>(), cancellationToken).ConfigureAwait(false),
|
||||
DataTypeEnum.Int16 => await WriteAsync(address, jArray.ToObject<Int16[]>(), cancellationToken: cancellationToken).ConfigureAwait(false),
|
||||
DataTypeEnum.UInt16 => await WriteAsync(address, jArray.ToObject<UInt16[]>(), cancellationToken: cancellationToken).ConfigureAwait(false),
|
||||
DataTypeEnum.Int32 => await WriteAsync(address, jArray.ToObject<Int32[]>(), cancellationToken: cancellationToken).ConfigureAwait(false),
|
||||
DataTypeEnum.UInt32 => await WriteAsync(address, jArray.ToObject<UInt32[]>(), cancellationToken: cancellationToken).ConfigureAwait(false),
|
||||
DataTypeEnum.Int64 => await WriteAsync(address, jArray.ToObject<Int64[]>(), cancellationToken: cancellationToken).ConfigureAwait(false),
|
||||
DataTypeEnum.UInt64 => await WriteAsync(address, jArray.ToObject<UInt64[]>(), cancellationToken: cancellationToken).ConfigureAwait(false),
|
||||
DataTypeEnum.Single => await WriteAsync(address, jArray.ToObject<Single[]>(), cancellationToken: cancellationToken).ConfigureAwait(false),
|
||||
DataTypeEnum.Double => await WriteAsync(address, jArray.ToObject<Double[]>(), cancellationToken: cancellationToken).ConfigureAwait(false),
|
||||
_ => new OperResult(DefaultResource.Localizer["DataTypeNotSupported", dataType]),
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
var bitConverter = ThingsGatewayBitConverter.GetTransByAddress(ref address);
|
||||
if (bitConverter.ArrayLength > 1)
|
||||
{
|
||||
return new OperResult("The array length is explicitly configured in the variable address, but the written value is not an array");
|
||||
}
|
||||
return dataType switch
|
||||
{
|
||||
DataTypeEnum.String => await WriteAsync(address, value.ToObject<String>(), bitConverter, cancellationToken).ConfigureAwait(false),
|
||||
DataTypeEnum.Boolean => await WriteAsync(address, value.ToObject<Boolean>(), bitConverter, cancellationToken).ConfigureAwait(false),
|
||||
DataTypeEnum.Byte => await WriteAsync(address, value.ToObject<Byte>(), bitConverter, cancellationToken).ConfigureAwait(false),
|
||||
DataTypeEnum.Int16 => await WriteAsync(address, value.ToObject<Int16>(), bitConverter, cancellationToken).ConfigureAwait(false),
|
||||
DataTypeEnum.UInt16 => await WriteAsync(address, value.ToObject<UInt16>(), bitConverter, cancellationToken).ConfigureAwait(false),
|
||||
DataTypeEnum.Int32 => await WriteAsync(address, value.ToObject<Int32>(), bitConverter, cancellationToken).ConfigureAwait(false),
|
||||
DataTypeEnum.UInt32 => await WriteAsync(address, value.ToObject<UInt32>(), bitConverter, cancellationToken).ConfigureAwait(false),
|
||||
DataTypeEnum.Int64 => await WriteAsync(address, value.ToObject<Int64>(), bitConverter, cancellationToken).ConfigureAwait(false),
|
||||
DataTypeEnum.UInt64 => await WriteAsync(address, value.ToObject<UInt64>(), bitConverter, cancellationToken).ConfigureAwait(false),
|
||||
DataTypeEnum.Single => await WriteAsync(address, value.ToObject<Single>(), bitConverter, cancellationToken).ConfigureAwait(false),
|
||||
DataTypeEnum.Double => await WriteAsync(address, value.ToObject<Double>(), bitConverter, cancellationToken).ConfigureAwait(false),
|
||||
_ => new OperResult(DefaultResource.Localizer["DataTypeNotSupported", dataType]),
|
||||
};
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new OperResult(ex);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion 动态类型读写
|
||||
|
||||
#region 读取
|
||||
|
||||
/// <inheritdoc/>
|
||||
public abstract ValueTask<OperResult<byte[]>> ReadAsync(string address, int length, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual async ValueTask<OperResult<Boolean[]>> ReadBooleanAsync(string address, int length, IThingsGatewayBitConverter bitConverter = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
bitConverter ??= ThingsGatewayBitConverter.GetTransByAddress(ref address);
|
||||
|
||||
var result = await ReadAsync(address, GetLength(address, length, RegisterByteLength, true), cancellationToken).ConfigureAwait(false);
|
||||
|
||||
return result.OperResultFrom(() => bitConverter.ToBoolean(result.Content, GetBitOffsetDefault(address), length, BitReverse(address)));
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual async ValueTask<OperResult<Int16[]>> ReadInt16Async(string address, int length, IThingsGatewayBitConverter bitConverter = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
bitConverter ??= ThingsGatewayBitConverter.GetTransByAddress(ref address);
|
||||
var result = await ReadAsync(address, GetLength(address, length, 2), cancellationToken).ConfigureAwait(false);
|
||||
return result.OperResultFrom(() => bitConverter.ToInt16(result.Content, 0, length));
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual async ValueTask<OperResult<UInt16[]>> ReadUInt16Async(string address, int length, IThingsGatewayBitConverter bitConverter = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
bitConverter ??= ThingsGatewayBitConverter.GetTransByAddress(ref address);
|
||||
var result = await ReadAsync(address, GetLength(address, length, 2), cancellationToken).ConfigureAwait(false);
|
||||
return result.OperResultFrom(() => bitConverter.ToUInt16(result.Content, 0, length));
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual async ValueTask<OperResult<Int32[]>> ReadInt32Async(string address, int length, IThingsGatewayBitConverter bitConverter = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
bitConverter ??= ThingsGatewayBitConverter.GetTransByAddress(ref address);
|
||||
var result = await ReadAsync(address, GetLength(address, length, 4), cancellationToken).ConfigureAwait(false);
|
||||
return result.OperResultFrom(() => bitConverter.ToInt32(result.Content, 0, length));
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual async ValueTask<OperResult<UInt32[]>> ReadUInt32Async(string address, int length, IThingsGatewayBitConverter bitConverter = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
bitConverter ??= ThingsGatewayBitConverter.GetTransByAddress(ref address);
|
||||
var result = await ReadAsync(address, GetLength(address, length, 4), cancellationToken).ConfigureAwait(false);
|
||||
return result.OperResultFrom(() => bitConverter.ToUInt32(result.Content, 0, length));
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual async ValueTask<OperResult<Int64[]>> ReadInt64Async(string address, int length, IThingsGatewayBitConverter bitConverter = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
bitConverter ??= ThingsGatewayBitConverter.GetTransByAddress(ref address);
|
||||
var result = await ReadAsync(address, GetLength(address, length, 8), cancellationToken).ConfigureAwait(false);
|
||||
return result.OperResultFrom(() => bitConverter.ToInt64(result.Content, 0, length));
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual async ValueTask<OperResult<UInt64[]>> ReadUInt64Async(string address, int length, IThingsGatewayBitConverter bitConverter = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
bitConverter ??= ThingsGatewayBitConverter.GetTransByAddress(ref address);
|
||||
var result = await ReadAsync(address, GetLength(address, length, 8), cancellationToken).ConfigureAwait(false);
|
||||
return result.OperResultFrom(() => bitConverter.ToUInt64(result.Content, 0, length));
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual async ValueTask<OperResult<Single[]>> ReadSingleAsync(string address, int length, IThingsGatewayBitConverter bitConverter = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
bitConverter ??= ThingsGatewayBitConverter.GetTransByAddress(ref address);
|
||||
var result = await ReadAsync(address, GetLength(address, length, 4), cancellationToken).ConfigureAwait(false);
|
||||
return result.OperResultFrom(() => bitConverter.ToSingle(result.Content, 0, length));
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual async ValueTask<OperResult<Double[]>> ReadDoubleAsync(string address, int length, IThingsGatewayBitConverter bitConverter = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
bitConverter ??= ThingsGatewayBitConverter.GetTransByAddress(ref address);
|
||||
var result = await ReadAsync(address, GetLength(address, length, 8), cancellationToken).ConfigureAwait(false);
|
||||
return result.OperResultFrom(() => bitConverter.ToDouble(result.Content, 0, length));
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual async ValueTask<OperResult<String[]>> ReadStringAsync(string address, int length, IThingsGatewayBitConverter bitConverter = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
bitConverter ??= ThingsGatewayBitConverter.GetTransByAddress(ref address);
|
||||
if (bitConverter.StringLength == null) return new OperResult<String[]>(DefaultResource.Localizer["StringAddressError"]);
|
||||
var len = bitConverter.StringLength * length;
|
||||
|
||||
var result = await ReadAsync(address, GetLength(address, len.Value, 1), cancellationToken).ConfigureAwait(false);
|
||||
return result.OperResultFrom(() =>
|
||||
{
|
||||
List<String> strings = new();
|
||||
for (int i = 0; i < length; i++)
|
||||
{
|
||||
var data = bitConverter.ToString(result.Content, i * bitConverter.StringLength.Value, bitConverter.StringLength.Value);
|
||||
strings.Add(data);
|
||||
}
|
||||
return strings.ToArray();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#endregion 读取
|
||||
|
||||
#region 写入
|
||||
|
||||
/// <inheritdoc/>
|
||||
public abstract ValueTask<OperResult> WriteAsync(string address, byte[] value, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public abstract ValueTask<OperResult> WriteAsync(string address, bool[] value, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual ValueTask<OperResult> WriteAsync(string address, bool value, IThingsGatewayBitConverter bitConverter = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return WriteAsync(address, new bool[1] { value }, cancellationToken);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual ValueTask<OperResult> WriteAsync(string address, byte value, IThingsGatewayBitConverter bitConverter = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
bitConverter ??= ThingsGatewayBitConverter.GetTransByAddress(ref address);
|
||||
return WriteAsync(address, new byte[] { value }, cancellationToken);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual ValueTask<OperResult> WriteAsync(string address, short value, IThingsGatewayBitConverter bitConverter = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
bitConverter ??= ThingsGatewayBitConverter.GetTransByAddress(ref address);
|
||||
return WriteAsync(address, bitConverter.GetBytes(value), cancellationToken);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual ValueTask<OperResult> WriteAsync(string address, ushort value, IThingsGatewayBitConverter bitConverter = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
bitConverter ??= ThingsGatewayBitConverter.GetTransByAddress(ref address);
|
||||
return WriteAsync(address, bitConverter.GetBytes(value), cancellationToken);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual ValueTask<OperResult> WriteAsync(string address, int value, IThingsGatewayBitConverter bitConverter = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
bitConverter ??= ThingsGatewayBitConverter.GetTransByAddress(ref address);
|
||||
return WriteAsync(address, bitConverter.GetBytes(value), cancellationToken);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual ValueTask<OperResult> WriteAsync(string address, uint value, IThingsGatewayBitConverter bitConverter = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
bitConverter ??= ThingsGatewayBitConverter.GetTransByAddress(ref address);
|
||||
return WriteAsync(address, bitConverter.GetBytes(value), cancellationToken);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual ValueTask<OperResult> WriteAsync(string address, long value, IThingsGatewayBitConverter bitConverter = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
bitConverter ??= ThingsGatewayBitConverter.GetTransByAddress(ref address);
|
||||
return WriteAsync(address, bitConverter.GetBytes(value), cancellationToken);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual ValueTask<OperResult> WriteAsync(string address, ulong value, IThingsGatewayBitConverter bitConverter = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
bitConverter ??= ThingsGatewayBitConverter.GetTransByAddress(ref address);
|
||||
return WriteAsync(address, bitConverter.GetBytes(value), cancellationToken);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual ValueTask<OperResult> WriteAsync(string address, float value, IThingsGatewayBitConverter bitConverter = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
bitConverter ??= ThingsGatewayBitConverter.GetTransByAddress(ref address);
|
||||
return WriteAsync(address, bitConverter.GetBytes(value), cancellationToken);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual ValueTask<OperResult> WriteAsync(string address, double value, IThingsGatewayBitConverter bitConverter = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
bitConverter ??= ThingsGatewayBitConverter.GetTransByAddress(ref address);
|
||||
return WriteAsync(address, bitConverter.GetBytes(value), cancellationToken);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual ValueTask<OperResult> WriteAsync(string address, string value, IThingsGatewayBitConverter bitConverter = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
bitConverter ??= ThingsGatewayBitConverter.GetTransByAddress(ref address);
|
||||
var data = bitConverter.GetBytes(value);
|
||||
return WriteAsync(address, data.ArrayExpandToLength(bitConverter.StringLength ?? data.Length), cancellationToken);
|
||||
}
|
||||
|
||||
#endregion 写入
|
||||
|
||||
#region 写入数组
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual ValueTask<OperResult> WriteAsync(string address, short[] value, IThingsGatewayBitConverter bitConverter = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
bitConverter ??= ThingsGatewayBitConverter.GetTransByAddress(ref address);
|
||||
return WriteAsync(address, bitConverter.GetBytes(value), cancellationToken);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual ValueTask<OperResult> WriteAsync(string address, ushort[] value, IThingsGatewayBitConverter bitConverter = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
bitConverter ??= ThingsGatewayBitConverter.GetTransByAddress(ref address);
|
||||
return WriteAsync(address, bitConverter.GetBytes(value), cancellationToken);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual ValueTask<OperResult> WriteAsync(string address, int[] value, IThingsGatewayBitConverter bitConverter = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
bitConverter ??= ThingsGatewayBitConverter.GetTransByAddress(ref address);
|
||||
return WriteAsync(address, bitConverter.GetBytes(value), cancellationToken);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual ValueTask<OperResult> WriteAsync(string address, uint[] value, IThingsGatewayBitConverter bitConverter = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
bitConverter ??= ThingsGatewayBitConverter.GetTransByAddress(ref address);
|
||||
return WriteAsync(address, bitConverter.GetBytes(value), cancellationToken);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual ValueTask<OperResult> WriteAsync(string address, long[] value, IThingsGatewayBitConverter bitConverter = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
bitConverter ??= ThingsGatewayBitConverter.GetTransByAddress(ref address);
|
||||
return WriteAsync(address, bitConverter.GetBytes(value), cancellationToken);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual ValueTask<OperResult> WriteAsync(string address, ulong[] value, IThingsGatewayBitConverter bitConverter = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
bitConverter ??= ThingsGatewayBitConverter.GetTransByAddress(ref address);
|
||||
return WriteAsync(address, bitConverter.GetBytes(value), cancellationToken);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual ValueTask<OperResult> WriteAsync(string address, float[] value, IThingsGatewayBitConverter bitConverter = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
bitConverter ??= ThingsGatewayBitConverter.GetTransByAddress(ref address);
|
||||
return WriteAsync(address, bitConverter.GetBytes(value), cancellationToken);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual ValueTask<OperResult> WriteAsync(string address, double[] value, IThingsGatewayBitConverter bitConverter = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
bitConverter ??= ThingsGatewayBitConverter.GetTransByAddress(ref address);
|
||||
return WriteAsync(address, bitConverter.GetBytes(value), cancellationToken);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual async ValueTask<OperResult> WriteAsync(string address, string[] value, IThingsGatewayBitConverter bitConverter = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
bitConverter ??= ThingsGatewayBitConverter.GetTransByAddress(ref address);
|
||||
if (bitConverter.StringLength == null) return new OperResult(DefaultResource.Localizer["StringAddressError"]);
|
||||
List<byte> bytes = new();
|
||||
foreach (var a in value)
|
||||
{
|
||||
var data = bitConverter.GetBytes(a);
|
||||
bytes.AddRange(data.ArrayExpandToLength(bitConverter.StringLength ?? data.Length));
|
||||
}
|
||||
return await WriteAsync(address, bytes.ToArray(), cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
#endregion 写入数组
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (Channel != null)
|
||||
{
|
||||
lock (Channel)
|
||||
{
|
||||
|
||||
if (Channel.Collects.Count == 1)
|
||||
{
|
||||
try
|
||||
{
|
||||
//只关闭,不释放
|
||||
Channel.SafeClose();
|
||||
if (Channel is IClientChannel client)
|
||||
{
|
||||
client.WaitHandlePool.SafeDispose();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger?.LogWarning(ex);
|
||||
}
|
||||
}
|
||||
if (Channel is TcpServiceChannel tcpServiceChannel)
|
||||
{
|
||||
tcpServiceChannel.Clients.ForEach(a =>
|
||||
{
|
||||
a.WaitHandlePool.SafeDispose();
|
||||
});
|
||||
tcpServiceChannel.SafeClose();
|
||||
}
|
||||
Channel.Starting.Remove(ChannelStarting);
|
||||
Channel.Stoped.Remove(ChannelStoped);
|
||||
Channel.Started.Remove(ChannelStarted);
|
||||
Channel.Stoping.Remove(ChannelStoping);
|
||||
Channel.ChannelReceived.Remove(ChannelReceived);
|
||||
Channel.Collects.Remove(this);
|
||||
}
|
||||
}
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual Action<IPluginManager> ConfigurePlugins()
|
||||
{
|
||||
return a => { };
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace ThingsGateway.Foundation;
|
||||
|
||||
/// <summary>
|
||||
/// 协议基类
|
||||
/// </summary>
|
||||
public static partial class ProtocolBaseExtension
|
||||
{
|
||||
#region 读取
|
||||
|
||||
/// <inheritdoc/>
|
||||
public static async ValueTask<OperResult<Boolean>> ReadBooleanAsync(this IProtocol protocol, string address, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var result = await protocol.ReadBooleanAsync(address, 1, null, cancellationToken).ConfigureAwait(false);
|
||||
return result.OperResultFrom(() => result.Content[0]);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public static async ValueTask<OperResult<Double>> ReadDoubleAsync(this IProtocol protocol, string address, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var result = await protocol.ReadDoubleAsync(address, 1, null, cancellationToken).ConfigureAwait(false);
|
||||
return result.OperResultFrom(() => result.Content[0]);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public static async ValueTask<OperResult<Int16>> ReadInt16Async(this IProtocol protocol, string address, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var result = await protocol.ReadInt16Async(address, 1, null, cancellationToken).ConfigureAwait(false);
|
||||
return result.OperResultFrom(() => result.Content[0]);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public static async ValueTask<OperResult<Int32>> ReadInt32Async(this IProtocol protocol, string address, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var result = await protocol.ReadInt32Async(address, 1, null, cancellationToken).ConfigureAwait(false);
|
||||
return result.OperResultFrom(() => result.Content[0]);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public static async ValueTask<OperResult<Int64>> ReadInt64Async(this IProtocol protocol, string address, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var result = await protocol.ReadInt64Async(address, 1, null, cancellationToken).ConfigureAwait(false);
|
||||
return result.OperResultFrom(() => result.Content[0]);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public static async ValueTask<OperResult<Single>> ReadSingleAsync(this IProtocol protocol, string address, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var result = await protocol.ReadSingleAsync(address, 1, null, cancellationToken).ConfigureAwait(false);
|
||||
return result.OperResultFrom(() => result.Content[0]);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public static async ValueTask<OperResult<String>> ReadStringAsync(this IProtocol protocol, string address, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var result = await protocol.ReadStringAsync(address, 1, null, cancellationToken).ConfigureAwait(false);
|
||||
return result.OperResultFrom(() => result.Content[0]);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public static async ValueTask<OperResult<UInt16>> ReadUInt16Async(this IProtocol protocol, string address, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var result = await protocol.ReadUInt16Async(address, 1, null, cancellationToken).ConfigureAwait(false);
|
||||
return result.OperResultFrom(() => result.Content[0]);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public static async ValueTask<OperResult<UInt32>> ReadUInt32Async(this IProtocol protocol, string address, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var result = await protocol.ReadUInt32Async(address, 1, null, cancellationToken).ConfigureAwait(false);
|
||||
return result.OperResultFrom(() => result.Content[0]);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public static async ValueTask<OperResult<UInt64>> ReadUInt64Async(this IProtocol protocol, string address, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var result = await protocol.ReadUInt64Async(address, 1, null, cancellationToken).ConfigureAwait(false);
|
||||
return result.OperResultFrom(() => result.Content[0]);
|
||||
}
|
||||
|
||||
#endregion 读取
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using TouchSocket.Resources;
|
||||
|
||||
namespace ThingsGateway.Foundation;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public static partial class ProtocolWaitDataStatusExtension
|
||||
{
|
||||
/// <summary>
|
||||
/// 当状态不是<see cref="WaitDataStatus.SetRunning"/>时返回异常。
|
||||
/// </summary>
|
||||
/// <param name="status"></param>
|
||||
public static OperResult Check(this WaitDataStatus status)
|
||||
{
|
||||
switch (status)
|
||||
{
|
||||
case WaitDataStatus.SetRunning:
|
||||
return new();
|
||||
|
||||
case WaitDataStatus.Canceled: return new(new OperationCanceledException());
|
||||
case WaitDataStatus.Overtime: return new(new TimeoutException());
|
||||
case WaitDataStatus.Disposed:
|
||||
case WaitDataStatus.Default:
|
||||
default:
|
||||
{
|
||||
return new(new Exception(TouchSocketCoreResource.UnknownError));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace ThingsGateway.Foundation;
|
||||
|
||||
/// <summary>
|
||||
/// StructContentPraseExtensions
|
||||
/// </summary>
|
||||
public static class StructContentPraseExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// 在返回的字节数组中解析每个变量的值
|
||||
/// 根据每个变量的<see cref="IVariable.Index"/>
|
||||
/// 不支持变长字符串类型变量,不能存在于变量List中
|
||||
/// </summary>
|
||||
/// <param name="protocol">设备</param>
|
||||
/// <param name="variables">设备变量List</param>
|
||||
/// <param name="buffer">返回的字节数组</param>
|
||||
/// <param name="exWhenAny">任意一个失败时抛出异常</param>
|
||||
/// <returns>解析结果</returns>
|
||||
public static OperResult PraseStructContent<T>(this IEnumerable<T> variables, IProtocol protocol, byte[] buffer, bool exWhenAny) where T : IVariable
|
||||
{
|
||||
var time = DateTime.Now;
|
||||
var result = OperResult.Success;
|
||||
foreach (var variable in variables)
|
||||
{
|
||||
IThingsGatewayBitConverter byteConverter = variable.ThingsGatewayBitConverter;
|
||||
var dataType = variable.DataType;
|
||||
int index = variable.Index;
|
||||
try
|
||||
{
|
||||
var data = byteConverter.GetDataFormBytes(protocol, variable.RegisterAddress, buffer, index, dataType);
|
||||
result = Set(variable, data);
|
||||
if (exWhenAny)
|
||||
if (!result.IsSuccess)
|
||||
return result;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new OperResult($"Error parsing byte array, address: {variable.RegisterAddress}, array length: {buffer.Length}, index: {index}, type: {dataType}", ex);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
OperResult Set(IVariable organizedVariable, object num)
|
||||
{
|
||||
return organizedVariable.SetValue(num, time);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Import Project="$(SolutionDir)Foundation.props" />
|
||||
<Import Project="$(SolutionDir)PackNuget.props" />
|
||||
<Import Project="$(SolutionDir)FoundationVersion.props" />
|
||||
|
||||
<PropertyGroup>
|
||||
<Description>工业设备通讯协议-基础类库</Description>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.Localization.Abstractions" Version="8.0.10" />
|
||||
<PackageReference Include="ThingsGateway.NewLife.X" Version="$(AdminVersion)" />
|
||||
<PackageReference Include="TouchSocket" Version="2.1.8" />
|
||||
<PackageReference Include="TouchSocket.SerialPorts" Version="2.1.8" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,61 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
#if NET6_0_OR_GREATER
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Text.Json;
|
||||
|
||||
#endif
|
||||
|
||||
using Newtonsoft.Json;
|
||||
|
||||
using System.Text;
|
||||
|
||||
namespace ThingsGateway.Foundation;
|
||||
|
||||
#if NET6_0_OR_GREATER
|
||||
|
||||
/// <inheritdoc/>
|
||||
public class EncodingConverter : System.Text.Json.Serialization.JsonConverter<Encoding>
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public override Encoding? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||
{
|
||||
// 从 JSON 中读取编码名称,并创建对应的 Encoding 对象
|
||||
string? encodingName = reader.GetString();
|
||||
return Encoding.GetEncoding(encodingName ?? Encoding.UTF8.WebName);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Write(Utf8JsonWriter writer, Encoding value, JsonSerializerOptions options)
|
||||
{
|
||||
// 将 Encoding 对象的编码名称作为字符串写入 JSON
|
||||
writer.WriteStringValue(value?.WebName);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <inheritdoc/>
|
||||
public class NewtonsoftEncodingConverter : Newtonsoft.Json.JsonConverter<Encoding>
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public override Encoding? ReadJson(JsonReader reader, Type objectType, Encoding? existingValue, bool hasExistingValue, Newtonsoft.Json.JsonSerializer serializer)
|
||||
{
|
||||
// 从 JSON 字符串中读取编码名称,并创建相应的 Encoding 对象
|
||||
string? encodingName = reader.Value as string;
|
||||
return Encoding.GetEncoding(encodingName ?? Encoding.UTF8.WebName);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void WriteJson(JsonWriter writer, Encoding? value, Newtonsoft.Json.JsonSerializer serializer)
|
||||
{
|
||||
writer.WriteValue(value.WebName);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,341 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
#if NET6_0_OR_GREATER
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
#endif
|
||||
|
||||
using Newtonsoft.Json;
|
||||
|
||||
using System.Text;
|
||||
|
||||
namespace ThingsGateway.Foundation;
|
||||
|
||||
/// <summary>
|
||||
/// 类型转换
|
||||
/// </summary>
|
||||
public interface IThingsGatewayBitConverter
|
||||
{
|
||||
/// <summary>
|
||||
/// 指定大小端。
|
||||
/// </summary>
|
||||
EndianType EndianType { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 当前的字符串编码类型
|
||||
/// </summary>
|
||||
#if NET6_0_OR_GREATER
|
||||
[System.Text.Json.Serialization.JsonConverter(typeof(EncodingConverter))]
|
||||
[JsonProperty(ItemConverterType = typeof(NewtonsoftEncodingConverter))]
|
||||
#else
|
||||
|
||||
[JsonProperty(ItemConverterType = typeof(NewtonsoftEncodingConverter))]
|
||||
#endif
|
||||
Encoding Encoding { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 当前的Bcd编码类型
|
||||
/// </summary>
|
||||
BcdFormatEnum? BcdFormat { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 字符串长度
|
||||
/// </summary>
|
||||
int? StringLength { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 数组长度,只在连读时生效
|
||||
/// </summary>
|
||||
int? ArrayLength { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置在解析字符串的时候是否将字节按照字单位反转
|
||||
/// </summary>
|
||||
bool IsStringReverseByteWord { get; set; }
|
||||
/// <summary>
|
||||
/// 获取或设置在解析字符串的时候是否变长字符串
|
||||
/// </summary>
|
||||
bool IsVariableStringLength { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 4字节解析规则
|
||||
/// </summary>
|
||||
DataFormatEnum DataFormat { get; set; }
|
||||
|
||||
#region GetBytes
|
||||
|
||||
/// <summary>
|
||||
/// 转换为指定端16字节
|
||||
/// </summary>
|
||||
/// <param name="value"></param>
|
||||
/// <returns></returns>
|
||||
byte[] GetBytes(decimal value);
|
||||
|
||||
/// <summary>
|
||||
/// 转换为指定端2字节
|
||||
/// </summary>
|
||||
/// <param name="value"></param>
|
||||
/// <returns></returns>
|
||||
byte[] GetBytes(char value);
|
||||
|
||||
/// <summary>
|
||||
/// bool变量转化缓存数据,一般来说单bool只能转化为0x01 或是 0x00<br />
|
||||
/// </summary>
|
||||
/// <param name="value">等待转化的数据</param>
|
||||
/// <returns>buffer数据</returns>
|
||||
byte[] GetBytes(bool value);
|
||||
|
||||
/// <summary>
|
||||
/// 将bool数组变量转化缓存数据,如果数组长度不满足8的倍数,则自动补0操作。<br />
|
||||
/// </summary>
|
||||
/// <param name="values">等待转化的数组</param>
|
||||
/// <returns>buffer数据</returns>
|
||||
byte[] GetBytes(bool[] values);
|
||||
|
||||
/// <summary>
|
||||
/// short变量转化缓存数据,一个short数据可以转为2个字节的byte数组<br />
|
||||
/// </summary>
|
||||
/// <param name="value">等待转化的数据</param>
|
||||
/// <returns>buffer数据</returns>
|
||||
byte[] GetBytes(short value);
|
||||
|
||||
/// <inheritdoc/>
|
||||
byte[] GetBytes(short[] value);
|
||||
|
||||
/// <inheritdoc/>
|
||||
byte[] GetBytes(ushort[] value);
|
||||
|
||||
/// <inheritdoc/>
|
||||
byte[] GetBytes(int[] value);
|
||||
|
||||
/// <inheritdoc/>
|
||||
byte[] GetBytes(uint[] value);
|
||||
|
||||
/// <inheritdoc/>
|
||||
byte[] GetBytes(long[] value);
|
||||
|
||||
/// <inheritdoc/>
|
||||
byte[] GetBytes(ulong[] value);
|
||||
|
||||
/// <inheritdoc/>
|
||||
byte[] GetBytes(float[] value);
|
||||
|
||||
/// <inheritdoc/>
|
||||
byte[] GetBytes(double[] value);
|
||||
|
||||
/// <summary>
|
||||
/// ushort变量转化缓存数据,一个ushort数据可以转为2个字节的Byte数组<br />
|
||||
/// </summary>
|
||||
/// <param name="value">等待转化的数据</param>
|
||||
/// <returns>buffer数据</returns>
|
||||
byte[] GetBytes(ushort value);
|
||||
|
||||
/// <summary>
|
||||
/// int变量转化缓存数据,一个int数据可以转为4个字节的byte数组<br />
|
||||
/// </summary>
|
||||
/// <param name="value">等待转化的数据</param>
|
||||
/// <returns>buffer数据</returns>
|
||||
byte[] GetBytes(int value);
|
||||
|
||||
/// <summary>
|
||||
/// uint变量转化缓存数据,一个uint数据可以转为4个字节的byte数组<br />
|
||||
/// </summary>
|
||||
/// <param name="value">等待转化的数据</param>
|
||||
/// <returns>buffer数据</returns>
|
||||
byte[] GetBytes(uint value);
|
||||
|
||||
/// <summary>
|
||||
/// long变量转化缓存数据,一个long数据可以转为8个字节的byte数组<br />
|
||||
/// </summary>
|
||||
/// <param name="value">等待转化的数据</param>
|
||||
/// <returns>buffer数据</returns>
|
||||
byte[] GetBytes(long value);
|
||||
|
||||
/// <summary>
|
||||
/// ulong变量转化缓存数据,一个ulong数据可以转为8个字节的byte数组<br />
|
||||
/// </summary>
|
||||
/// <param name="value">等待转化的数据</param>
|
||||
/// <returns>buffer数据</returns>
|
||||
byte[] GetBytes(ulong value);
|
||||
|
||||
/// <summary>
|
||||
/// float变量转化缓存数据,一个float数据可以转为4个字节的byte数组<br />
|
||||
/// </summary>
|
||||
/// <param name="value">等待转化的数据</param>
|
||||
/// <returns>buffer数据</returns>
|
||||
byte[] GetBytes(float value);
|
||||
|
||||
/// <summary>
|
||||
/// double变量转化缓存数据,一个double数据可以转为8个字节的byte数组<br />
|
||||
/// </summary>
|
||||
/// <param name="value">等待转化的数据</param>
|
||||
/// <returns>buffer数据</returns>
|
||||
byte[] GetBytes(double value);
|
||||
|
||||
/// <summary>
|
||||
/// 使用指定的编码字符串转化缓存数据<br />
|
||||
/// </summary>
|
||||
/// <param name="value">等待转化的数据</param>
|
||||
/// <returns>buffer数据</returns>
|
||||
byte[] GetBytes(string value);
|
||||
|
||||
#endregion GetBytes
|
||||
|
||||
#region ToValue
|
||||
|
||||
/// <summary>
|
||||
/// 从缓存中提取出bool结果,需要传入想要提取的位索引,注意:是从0开始的位索引,10则表示 buffer[1] 的第二位。<br />
|
||||
/// </summary>
|
||||
/// <param name="buffer">等待提取的缓存数据</param>
|
||||
/// <param name="offset">位的索引,注意:是从0开始的位索引,10则表示 buffer[1] 的第二位。</param>
|
||||
/// <param name="isReverse">是否需要按字反转</param>
|
||||
bool ToBoolean(byte[] buffer, int offset, bool isReverse);
|
||||
|
||||
/// <inheritdoc/>
|
||||
bool[] ToBoolean(byte[] buffer, int offset, int len, bool isReverse);
|
||||
|
||||
/// <inheritdoc/>
|
||||
byte ToByte(byte[] buffer, int offset);
|
||||
|
||||
/// <inheritdoc/>
|
||||
byte[] ToByte(byte[] buffer, int offset, int length);
|
||||
|
||||
/// <summary>
|
||||
/// 从缓存中提取double结果,需要指定起始的字节索引,按照字节为单位,一个double占用八个字节<br />
|
||||
/// </summary>
|
||||
/// <param name="buffer">缓存对象</param>
|
||||
/// <param name="offset">索引位置</param>
|
||||
/// <returns>double对象</returns>
|
||||
double ToDouble(byte[] buffer, int offset);
|
||||
|
||||
/// <inheritdoc/>
|
||||
double[] ToDouble(byte[] buffer, int offset, int len);
|
||||
|
||||
/// <summary>
|
||||
/// 从缓存中提取short结果,需要指定起始的字节索引,按照字节为单位,一个short占用两个字节<br />
|
||||
/// </summary>
|
||||
/// <param name="buffer">缓存数据</param>
|
||||
/// <param name="offset">索引位置</param>
|
||||
/// <returns>short对象</returns>
|
||||
short ToInt16(byte[] buffer, int offset);
|
||||
|
||||
/// <inheritdoc/>
|
||||
short[] ToInt16(byte[] buffer, int offset, int len);
|
||||
|
||||
/// <summary>
|
||||
/// 从缓存中提取int结果,需要指定起始的字节索引,按照字节为单位,一个int占用四个字节<br />
|
||||
/// </summary>
|
||||
/// <param name="buffer">缓存数据</param>
|
||||
/// <param name="offset">索引位置</param>
|
||||
/// <returns>int对象</returns>
|
||||
int ToInt32(byte[] buffer, int offset);
|
||||
|
||||
/// <inheritdoc/>
|
||||
int[] ToInt32(byte[] buffer, int offset, int len);
|
||||
|
||||
/// <summary>
|
||||
/// 从缓存中提取long结果,需要指定起始的字节索引,按照字节为单位,一个long占用八个字节<br />
|
||||
/// </summary>
|
||||
/// <param name="buffer">缓存数据</param>
|
||||
/// <param name="offset">索引位置</param>
|
||||
/// <returns>long对象</returns>
|
||||
long ToInt64(byte[] buffer, int offset);
|
||||
|
||||
/// <inheritdoc/>
|
||||
long[] ToInt64(byte[] buffer, int offset, int len);
|
||||
|
||||
/// <summary>
|
||||
/// 从缓存中提取float结果,需要指定起始的字节索引,按照字节为单位,一个float占用四个字节<b />
|
||||
/// </summary>
|
||||
/// <param name="buffer">缓存对象</param>
|
||||
/// <param name="offset">索引位置</param>
|
||||
/// <returns>float对象</returns>
|
||||
float ToSingle(byte[] buffer, int offset);
|
||||
|
||||
/// <inheritdoc/>
|
||||
float[] ToSingle(byte[] buffer, int offset, int len);
|
||||
|
||||
/// <summary>
|
||||
/// 从缓存中的部分字节数组转化为string结果,使用指定的编码,指定起始的字节索引,字节长度信息。<br />
|
||||
/// </summary>
|
||||
/// <param name="buffer">缓存对象</param>
|
||||
/// <param name="offset">索引位置</param>
|
||||
/// <param name="length">byte数组长度</param>
|
||||
/// <returns>string对象</returns>
|
||||
string ToString(byte[] buffer, int offset, int length);
|
||||
|
||||
/// <summary>
|
||||
/// 从缓存中提取ushort结果,需要指定起始的字节索引,按照字节为单位,一个ushort占用两个字节<br />
|
||||
/// </summary>
|
||||
/// <param name="buffer">缓存数据</param>
|
||||
/// <param name="offset">索引位置</param>
|
||||
/// <returns>ushort对象</returns>
|
||||
ushort ToUInt16(byte[] buffer, int offset);
|
||||
|
||||
/// <inheritdoc/>
|
||||
ushort[] ToUInt16(byte[] buffer, int offset, int len);
|
||||
|
||||
/// <summary>
|
||||
/// 从缓存中提取uint结果,需要指定起始的字节索引,按照字节为单位,一个uint占用四个字节<br />
|
||||
/// </summary>
|
||||
/// <param name="buffer">缓存数据</param>
|
||||
/// <param name="offset">索引位置</param>
|
||||
/// <returns>uint对象</returns>
|
||||
uint ToUInt32(byte[] buffer, int offset);
|
||||
|
||||
/// <inheritdoc/>
|
||||
uint[] ToUInt32(byte[] buffer, int offset, int len);
|
||||
|
||||
/// <summary>
|
||||
/// 从缓存中提取ulong结果,需要指定起始的字节索引,按照字节为单位,一个ulong占用八个字节<b />
|
||||
/// </summary>
|
||||
/// <param name="buffer">缓存数据</param>
|
||||
/// <param name="offset">索引位置</param>
|
||||
/// <returns>ulong对象</returns>
|
||||
ulong ToUInt64(byte[] buffer, int offset);
|
||||
|
||||
/// <inheritdoc/>
|
||||
ulong[] ToUInt64(byte[] buffer, int offset, int len);
|
||||
|
||||
/// <summary>
|
||||
/// 转换为指定端模式的<see cref="decimal"/>数据。
|
||||
/// </summary>
|
||||
/// <param name="buffer"></param>
|
||||
/// <param name="offset"></param>
|
||||
/// <returns></returns>
|
||||
decimal ToDecimal(byte[] buffer, int offset);
|
||||
|
||||
/// <summary>
|
||||
/// 转换为指定端模式的Char数据。
|
||||
/// </summary>
|
||||
/// <param name="buffer"></param>
|
||||
/// <param name="offset"></param>
|
||||
/// <returns></returns>
|
||||
char ToChar(byte[] buffer, int offset);
|
||||
|
||||
/// <summary>
|
||||
/// 从缓存中提取decimal结果,需要指定起始的字节索引,按照字节为单位,一个decimal占用16个字节<b />
|
||||
/// </summary>
|
||||
/// <param name="buffer">缓存数据</param>
|
||||
/// <param name="offset">索引位置</param>
|
||||
/// <param name="length">length</param>
|
||||
/// <returns>decimal对象</returns>
|
||||
decimal[] ToDecimal(byte[] buffer, int offset, int length);
|
||||
|
||||
/// <summary>
|
||||
/// 获取指定的数据格式
|
||||
/// </summary>
|
||||
/// <param name="dataFormat"></param>
|
||||
/// <returns></returns>
|
||||
IThingsGatewayBitConverter GetByDataFormat(DataFormatEnum dataFormat);
|
||||
|
||||
#endregion ToValue
|
||||
}
|
||||
@@ -0,0 +1,937 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using Newtonsoft.Json;
|
||||
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
|
||||
using ThingsGateway.Foundation.Extension.Generic;
|
||||
|
||||
namespace ThingsGateway.Foundation;
|
||||
|
||||
/// <summary>
|
||||
/// 将基数据类型转换为指定端的一个字节数组,
|
||||
/// 或将一个字节数组转换为指定端基数据类型。
|
||||
/// </summary>
|
||||
public partial class ThingsGatewayBitConverter : IThingsGatewayBitConverter
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
#if NET6_0_OR_GREATER
|
||||
|
||||
[System.Text.Json.Serialization.JsonConverter(typeof(EncodingConverter))]
|
||||
[JsonConverter(typeof(NewtonsoftEncodingConverter))]
|
||||
public Encoding Encoding { get; set; } = Encoding.UTF8;
|
||||
#else
|
||||
|
||||
[JsonConverter(typeof(NewtonsoftEncodingConverter))]
|
||||
public Encoding Encoding { get; set; } = Encoding.UTF8;
|
||||
|
||||
#endif
|
||||
/// <inheritdoc/>
|
||||
public DataFormatEnum DataFormat { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual BcdFormatEnum? BcdFormat { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual int? StringLength { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual int? ArrayLength { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数
|
||||
/// </summary>
|
||||
public ThingsGatewayBitConverter()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数
|
||||
/// </summary>
|
||||
/// <param name="endianType"></param>
|
||||
public ThingsGatewayBitConverter(EndianType endianType)
|
||||
{
|
||||
EndianType = endianType;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual EndianType EndianType { get; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual bool IsStringReverseByteWord { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual bool IsVariableStringLength { get; set; }
|
||||
|
||||
internal TouchSocketBitConverter TouchSocketBitConverter => TouchSocketBitConverter.GetBitConverter(EndianType);
|
||||
|
||||
static ThingsGatewayBitConverter()
|
||||
{
|
||||
BigEndian = new ThingsGatewayBitConverter(EndianType.Big);
|
||||
LittleEndian = new ThingsGatewayBitConverter(EndianType.Little);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 以大端
|
||||
/// </summary>
|
||||
public static readonly ThingsGatewayBitConverter BigEndian;
|
||||
|
||||
/// <summary>
|
||||
/// 以小端
|
||||
/// </summary>
|
||||
public static readonly ThingsGatewayBitConverter LittleEndian;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual IThingsGatewayBitConverter GetByDataFormat(DataFormatEnum dataFormat)
|
||||
{
|
||||
var data = new ThingsGatewayBitConverter(EndianType);
|
||||
data.Encoding = Encoding;
|
||||
data.DataFormat = dataFormat;
|
||||
data.BcdFormat = BcdFormat;
|
||||
data.StringLength = StringLength;
|
||||
data.ArrayLength = ArrayLength;
|
||||
data.IsStringReverseByteWord = IsStringReverseByteWord;
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
#region GetBytes
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual byte[] GetBytes(string value)
|
||||
{
|
||||
if (string.IsNullOrEmpty(value))
|
||||
{
|
||||
return Array.Empty<byte>();
|
||||
}
|
||||
if (StringLength != null)
|
||||
{
|
||||
if (BcdFormat != null)
|
||||
{
|
||||
byte[] bytes = DataTransUtil.GetBytesFromBCD(value, BcdFormat.Value);
|
||||
return IsStringReverseByteWord ? bytes.BytesReverseByWord().ArrayExpandToLength(StringLength.Value) : bytes.ArrayExpandToLength(StringLength.Value);
|
||||
}
|
||||
else
|
||||
{
|
||||
byte[] bytes = Encoding.GetBytes(value);
|
||||
return IsStringReverseByteWord ? bytes.BytesReverseByWord().ArrayExpandToLength(StringLength.Value) : bytes.ArrayExpandToLength(StringLength.Value);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (BcdFormat != null)
|
||||
{
|
||||
byte[] bytes = DataTransUtil.GetBytesFromBCD(value, BcdFormat.Value);
|
||||
return IsStringReverseByteWord ? bytes.BytesReverseByWord() : bytes;
|
||||
}
|
||||
else
|
||||
{
|
||||
byte[] bytes = Encoding.GetBytes(value);
|
||||
return IsStringReverseByteWord ? bytes.BytesReverseByWord() : bytes;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual byte[] GetBytes(short[] value)
|
||||
{
|
||||
using ValueByteBlock byteBlock = new ValueByteBlock(value.Length * 2);
|
||||
for (int index = 0; index < value.Length; ++index)
|
||||
{
|
||||
byte[] bytes = GetBytes(value[index]);
|
||||
byteBlock.Write(bytes);
|
||||
}
|
||||
return byteBlock.ToArray();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual byte[] GetBytes(ushort[] value)
|
||||
{
|
||||
using ValueByteBlock byteBlock = new ValueByteBlock(value.Length * 2);
|
||||
for (int index = 0; index < value.Length; ++index)
|
||||
{
|
||||
byte[] bytes = GetBytes(value[index]);
|
||||
byteBlock.Write(bytes);
|
||||
}
|
||||
return byteBlock.ToArray();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual byte[] GetBytes(int[] value)
|
||||
{
|
||||
using ValueByteBlock byteBlock = new ValueByteBlock(value.Length * 4);
|
||||
for (int index = 0; index < value.Length; ++index)
|
||||
{
|
||||
byte[] bytes = GetBytes(value[index]);
|
||||
byteBlock.Write(bytes);
|
||||
}
|
||||
return byteBlock.ToArray();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual byte[] GetBytes(uint[] value)
|
||||
{
|
||||
using ValueByteBlock byteBlock = new ValueByteBlock(value.Length * 4);
|
||||
for (int index = 0; index < value.Length; ++index)
|
||||
{
|
||||
byte[] bytes = GetBytes(value[index]);
|
||||
byteBlock.Write(bytes);
|
||||
}
|
||||
return byteBlock.ToArray();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual byte[] GetBytes(long[] value)
|
||||
{
|
||||
using ValueByteBlock byteBlock = new ValueByteBlock(value.Length * 4);
|
||||
for (int index = 0; index < value.Length; ++index)
|
||||
{
|
||||
byte[] bytes = GetBytes(value[index]);
|
||||
byteBlock.Write(bytes);
|
||||
}
|
||||
return byteBlock.ToArray();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual byte[] GetBytes(ulong[] value)
|
||||
{
|
||||
using ValueByteBlock byteBlock = new ValueByteBlock(value.Length * 4);
|
||||
for (int index = 0; index < value.Length; ++index)
|
||||
{
|
||||
byte[] bytes = GetBytes(value[index]);
|
||||
byteBlock.Write(bytes);
|
||||
}
|
||||
return byteBlock.ToArray();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual byte[] GetBytes(float[] value)
|
||||
{
|
||||
using ValueByteBlock byteBlock = new ValueByteBlock(value.Length * 4);
|
||||
for (int index = 0; index < value.Length; ++index)
|
||||
{
|
||||
byte[] bytes = GetBytes(value[index]);
|
||||
byteBlock.Write(bytes);
|
||||
}
|
||||
return byteBlock.ToArray();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual byte[] GetBytes(double[] value)
|
||||
{
|
||||
using ValueByteBlock byteBlock = new ValueByteBlock(value.Length * 4);
|
||||
for (int index = 0; index < value.Length; ++index)
|
||||
{
|
||||
byte[] bytes = GetBytes(value[index]);
|
||||
byteBlock.Write(bytes);
|
||||
}
|
||||
return byteBlock.ToArray();
|
||||
}
|
||||
|
||||
#endregion GetBytes
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual string ToString(byte[] buffer, int offset, int len)
|
||||
{
|
||||
if (BcdFormat != null)
|
||||
{
|
||||
return IsStringReverseByteWord ? DataTransUtil.GetBcdValue(new ReadOnlySpan<byte>(buffer, offset, len).ToArray().BytesReverseByWord(), BcdFormat.Value) : DataTransUtil.GetBcdValue(new ReadOnlySpan<byte>(buffer, offset, len), BcdFormat.Value);
|
||||
}
|
||||
else
|
||||
{
|
||||
return IsStringReverseByteWord ?
|
||||
Encoding.GetString(new ReadOnlySpan<byte>(buffer, offset, len).ToArray().BytesReverseByWord()).TrimEnd().Replace($"\0", "") :
|
||||
Encoding.GetString(buffer, offset, len).TrimEnd().Replace($"\0", "");
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual bool ToBoolean(byte[] buffer, int offset, bool isReverse)
|
||||
{
|
||||
byte[] bytes;
|
||||
if (isReverse)
|
||||
bytes = buffer.BytesReverseByWord();
|
||||
else
|
||||
bytes = buffer.CopyArray();
|
||||
return bytes.GetBoolByIndex(offset);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual byte ToByte(byte[] buffer, int offset)
|
||||
{
|
||||
return buffer[offset];
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual short ToInt16(byte[] buffer, int offset)
|
||||
{
|
||||
return TouchSocketBitConverter.ToInt16(buffer, offset);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual int ToInt32(byte[] buffer, int offset)
|
||||
{
|
||||
if (buffer.Length - offset < 4)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(offset));
|
||||
}
|
||||
|
||||
unsafe
|
||||
{
|
||||
fixed (byte* p = &buffer[offset])
|
||||
{
|
||||
if (DataFormat == DataFormatEnum.DCBA)
|
||||
{
|
||||
return Unsafe.Read<int>(p);
|
||||
}
|
||||
else
|
||||
{
|
||||
ByteTransDataFormat4_Net6(ref buffer[offset]);
|
||||
var v = Unsafe.Read<int>(p);
|
||||
ByteTransDataFormat4_Net6(ref buffer[offset]);
|
||||
return v;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual long ToInt64(byte[] buffer, int offset)
|
||||
{
|
||||
if (buffer.Length - offset < 8)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(offset));
|
||||
}
|
||||
|
||||
unsafe
|
||||
{
|
||||
fixed (byte* p = &buffer[offset])
|
||||
{
|
||||
if (DataFormat == DataFormatEnum.DCBA)
|
||||
{
|
||||
return Unsafe.Read<long>(p);
|
||||
}
|
||||
else
|
||||
{
|
||||
ByteTransDataFormat8_Net6(ref buffer[offset]);
|
||||
var v = Unsafe.Read<long>(p);
|
||||
ByteTransDataFormat8_Net6(ref buffer[offset]);
|
||||
return v;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual ushort ToUInt16(byte[] buffer, int offset)
|
||||
{
|
||||
return TouchSocketBitConverter.ToUInt16(buffer, offset);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual uint ToUInt32(byte[] buffer, int offset)
|
||||
{
|
||||
if (buffer.Length - offset < 4)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(offset));
|
||||
}
|
||||
|
||||
unsafe
|
||||
{
|
||||
fixed (byte* p = &buffer[offset])
|
||||
{
|
||||
if (DataFormat == DataFormatEnum.DCBA)
|
||||
{
|
||||
return Unsafe.Read<uint>(p);
|
||||
}
|
||||
else
|
||||
{
|
||||
ByteTransDataFormat4_Net6(ref buffer[offset]);
|
||||
var v = Unsafe.Read<uint>(p);
|
||||
ByteTransDataFormat4_Net6(ref buffer[offset]);
|
||||
return v;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual ulong ToUInt64(byte[] buffer, int offset)
|
||||
{
|
||||
if (buffer.Length - offset < 8)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(offset));
|
||||
}
|
||||
|
||||
unsafe
|
||||
{
|
||||
fixed (byte* p = &buffer[offset])
|
||||
{
|
||||
if (DataFormat == DataFormatEnum.DCBA)
|
||||
{
|
||||
return Unsafe.Read<ulong>(p);
|
||||
}
|
||||
else
|
||||
{
|
||||
ByteTransDataFormat8_Net6(ref buffer[offset]);
|
||||
var v = Unsafe.Read<ulong>(p);
|
||||
ByteTransDataFormat8_Net6(ref buffer[offset]);
|
||||
return v;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual float ToSingle(byte[] buffer, int offset)
|
||||
{
|
||||
if (buffer.Length - offset < 4)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(offset));
|
||||
}
|
||||
|
||||
unsafe
|
||||
{
|
||||
fixed (byte* p = &buffer[offset])
|
||||
{
|
||||
if (DataFormat == DataFormatEnum.DCBA)
|
||||
{
|
||||
return Unsafe.Read<float>(p);
|
||||
}
|
||||
else
|
||||
{
|
||||
ByteTransDataFormat4_Net6(ref buffer[offset]);
|
||||
var v = Unsafe.Read<float>(p);
|
||||
ByteTransDataFormat4_Net6(ref buffer[offset]);
|
||||
return v;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual double ToDouble(byte[] buffer, int offset)
|
||||
{
|
||||
if (buffer.Length - offset < 8)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(offset));
|
||||
}
|
||||
|
||||
unsafe
|
||||
{
|
||||
fixed (byte* p = &buffer[offset])
|
||||
{
|
||||
if (DataFormat == DataFormatEnum.DCBA)
|
||||
{
|
||||
return Unsafe.Read<double>(p);
|
||||
}
|
||||
else
|
||||
{
|
||||
ByteTransDataFormat8_Net6(ref buffer[offset]);
|
||||
var v = Unsafe.Read<double>(p);
|
||||
ByteTransDataFormat8_Net6(ref buffer[offset]);
|
||||
return v;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual bool[] ToBoolean(byte[] buffer, int offset, int len, bool isReverse = false)
|
||||
{
|
||||
bool[] result = new bool[len];
|
||||
for (int i = 0; i < result.Length; i++)
|
||||
{
|
||||
result[i] = ToBoolean(buffer, offset + i, isReverse);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual byte[] ToByte(byte[] buffer, int offset, int length)
|
||||
{
|
||||
byte[] bytes = new byte[length];
|
||||
Array.Copy(buffer, offset, bytes, 0, bytes.Length);
|
||||
return bytes;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual double[] ToDouble(byte[] buffer, int offset, int len)
|
||||
{
|
||||
double[] numArray = new double[len];
|
||||
for (int index = 0; index < len; ++index)
|
||||
{
|
||||
numArray[index] = ToDouble(buffer, offset + 8 * index);
|
||||
}
|
||||
return numArray;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual short[] ToInt16(byte[] buffer, int offset, int len)
|
||||
{
|
||||
short[] numArray = new short[len];
|
||||
for (int index = 0; index < len; ++index)
|
||||
{
|
||||
numArray[index] = ToInt16(buffer, offset + 2 * index);
|
||||
}
|
||||
return numArray;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual int[] ToInt32(byte[] buffer, int offset, int len)
|
||||
{
|
||||
int[] numArray = new int[len];
|
||||
for (int index = 0; index < len; ++index)
|
||||
{
|
||||
numArray[index] = ToInt32(buffer, offset + 4 * index);
|
||||
}
|
||||
return numArray;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual long[] ToInt64(byte[] buffer, int offset, int len)
|
||||
{
|
||||
long[] numArray = new long[len];
|
||||
for (int index = 0; index < len; ++index)
|
||||
{
|
||||
numArray[index] = ToInt64(buffer, offset + 8 * index);
|
||||
}
|
||||
return numArray;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual float[] ToSingle(byte[] buffer, int offset, int len)
|
||||
{
|
||||
float[] numArray = new float[len];
|
||||
for (int index = 0; index < len; ++index)
|
||||
{
|
||||
numArray[index] = ToSingle(buffer, offset + 4 * index);
|
||||
}
|
||||
return numArray;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual ushort[] ToUInt16(byte[] buffer, int offset, int len)
|
||||
{
|
||||
ushort[] numArray = new ushort[len];
|
||||
for (int index = 0; index < len; ++index)
|
||||
{
|
||||
numArray[index] = ToUInt16(buffer, offset + 2 * index);
|
||||
}
|
||||
return numArray;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual uint[] ToUInt32(byte[] buffer, int offset, int len)
|
||||
{
|
||||
uint[] numArray = new uint[len];
|
||||
for (int index = 0; index < len; ++index)
|
||||
{
|
||||
numArray[index] = ToUInt32(buffer, offset + 4 * index);
|
||||
}
|
||||
return numArray;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual ulong[] ToUInt64(byte[] buffer, int offset, int len)
|
||||
{
|
||||
ulong[] numArray = new ulong[len];
|
||||
for (int index = 0; index < len; ++index)
|
||||
{
|
||||
numArray[index] = ToUInt64(buffer, offset + 8 * index);
|
||||
}
|
||||
return numArray;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual decimal[] ToDecimal(byte[] buffer, int offset, int len)
|
||||
{
|
||||
decimal[] numArray = new decimal[len];
|
||||
for (int index = 0; index < len; ++index)
|
||||
{
|
||||
numArray[index] = ToDecimal(buffer, offset + 8 * index);
|
||||
}
|
||||
return numArray;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual byte[] GetBytes(decimal value)
|
||||
{
|
||||
var bytes = new byte[16];
|
||||
Unsafe.As<byte, decimal>(ref bytes[0]) = value;
|
||||
if (DataFormat != DataFormatEnum.DCBA)
|
||||
{
|
||||
ByteTransDataFormat16_Net6(ref bytes[0]);
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual byte[] GetBytes(char value)
|
||||
{
|
||||
return TouchSocketBitConverter.GetBytes(value);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual byte[] GetBytes(bool value)
|
||||
{
|
||||
return TouchSocketBitConverter.GetBytes(value);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual byte[] GetBytes(bool[] values)
|
||||
{
|
||||
return TouchSocketBitConverter.GetBytes(values);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual byte[] GetBytes(short value)
|
||||
{
|
||||
return TouchSocketBitConverter.GetBytes(value);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual byte[] GetBytes(ushort value)
|
||||
{
|
||||
return TouchSocketBitConverter.GetBytes(value);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual byte[] GetBytes(int value)
|
||||
{
|
||||
var bytes = BitConverter.GetBytes(value);
|
||||
|
||||
if (DataFormat != DataFormatEnum.DCBA)
|
||||
bytes = ByteTransDataFormat4(bytes, 0);
|
||||
|
||||
return bytes;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual byte[] GetBytes(uint value)
|
||||
{
|
||||
var bytes = BitConverter.GetBytes(value);
|
||||
|
||||
if (DataFormat != DataFormatEnum.DCBA)
|
||||
bytes = ByteTransDataFormat4(bytes, 0);
|
||||
|
||||
return bytes;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual byte[] GetBytes(long value)
|
||||
{
|
||||
var bytes = BitConverter.GetBytes(value);
|
||||
|
||||
if (DataFormat != DataFormatEnum.DCBA)
|
||||
bytes = ByteTransDataFormat8(bytes, 0);
|
||||
|
||||
return bytes;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual byte[] GetBytes(ulong value)
|
||||
{
|
||||
var bytes = BitConverter.GetBytes(value);
|
||||
|
||||
if (DataFormat != DataFormatEnum.DCBA)
|
||||
bytes = ByteTransDataFormat8(bytes, 0);
|
||||
|
||||
return bytes;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual byte[] GetBytes(float value)
|
||||
{
|
||||
var bytes = BitConverter.GetBytes(value);
|
||||
|
||||
if (DataFormat != DataFormatEnum.DCBA)
|
||||
bytes = ByteTransDataFormat4(bytes, 0);
|
||||
|
||||
return bytes;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual byte[] GetBytes(double value)
|
||||
{
|
||||
var bytes = BitConverter.GetBytes(value);
|
||||
|
||||
if (DataFormat != DataFormatEnum.DCBA)
|
||||
bytes = ByteTransDataFormat8(bytes, 0);
|
||||
|
||||
return bytes;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual decimal ToDecimal(byte[] buffer, int offset)
|
||||
{
|
||||
if (buffer.Length - offset < 16)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(offset));
|
||||
}
|
||||
|
||||
unsafe
|
||||
{
|
||||
fixed (byte* p = &buffer[offset])
|
||||
{
|
||||
if (DataFormat == DataFormatEnum.DCBA)
|
||||
{
|
||||
return Unsafe.Read<decimal>(p);
|
||||
}
|
||||
else
|
||||
{
|
||||
ByteTransDataFormat16_Net6(ref buffer[offset]);
|
||||
var v = Unsafe.Read<decimal>(p);
|
||||
ByteTransDataFormat16_Net6(ref buffer[offset]);
|
||||
return v;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual char ToChar(byte[] buffer, int offset)
|
||||
{
|
||||
return TouchSocketBitConverter.ToChar(buffer, offset);
|
||||
}
|
||||
|
||||
#region Tool
|
||||
|
||||
/// <summary>反转多字节的数据信息</summary>
|
||||
/// <param name="value">数据字节</param>
|
||||
/// <param name="offset">起始索引,默认值为0</param>
|
||||
/// <returns>实际字节信息</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private byte[] ByteTransDataFormat4(byte[] value, int offset)
|
||||
|
||||
{
|
||||
var numArray = new byte[4];
|
||||
switch (DataFormat)
|
||||
{
|
||||
case DataFormatEnum.ABCD:
|
||||
numArray[0] = value[offset + 3];
|
||||
numArray[1] = value[offset + 2];
|
||||
numArray[2] = value[offset + 1];
|
||||
numArray[3] = value[offset];
|
||||
break;
|
||||
|
||||
case DataFormatEnum.BADC:
|
||||
numArray[0] = value[offset + 2];
|
||||
numArray[1] = value[offset + 3];
|
||||
numArray[2] = value[offset];
|
||||
numArray[3] = value[offset + 1];
|
||||
break;
|
||||
|
||||
case DataFormatEnum.CDAB:
|
||||
numArray[0] = value[offset + 1];
|
||||
numArray[1] = value[offset];
|
||||
numArray[2] = value[offset + 3];
|
||||
numArray[3] = value[offset + 2];
|
||||
break;
|
||||
|
||||
case DataFormatEnum.DCBA:
|
||||
numArray[0] = value[offset];
|
||||
numArray[1] = value[offset + 1];
|
||||
numArray[2] = value[offset + 2];
|
||||
numArray[3] = value[offset + 3];
|
||||
break;
|
||||
}
|
||||
return numArray;
|
||||
}
|
||||
|
||||
/// <summary>反转多字节的数据信息</summary>
|
||||
/// <param name="value">数据字节</param>
|
||||
/// <param name="offset">起始索引,默认值为0</param>
|
||||
/// <returns>实际字节信息</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private byte[] ByteTransDataFormat8(byte[] value, int offset)
|
||||
{
|
||||
var numArray = new byte[8];
|
||||
switch (DataFormat)
|
||||
{
|
||||
case DataFormatEnum.ABCD:
|
||||
numArray[0] = value[offset + 7];
|
||||
numArray[1] = value[offset + 6];
|
||||
numArray[2] = value[offset + 5];
|
||||
numArray[3] = value[offset + 4];
|
||||
numArray[4] = value[offset + 3];
|
||||
numArray[5] = value[offset + 2];
|
||||
numArray[6] = value[offset + 1];
|
||||
numArray[7] = value[offset];
|
||||
break;
|
||||
|
||||
case DataFormatEnum.BADC:
|
||||
numArray[0] = value[offset + 6];
|
||||
numArray[1] = value[offset + 7];
|
||||
numArray[2] = value[offset + 4];
|
||||
numArray[3] = value[offset + 5];
|
||||
numArray[4] = value[offset + 2];
|
||||
numArray[5] = value[offset + 3];
|
||||
numArray[6] = value[offset];
|
||||
numArray[7] = value[offset + 1];
|
||||
break;
|
||||
|
||||
case DataFormatEnum.CDAB:
|
||||
numArray[0] = value[offset + 1];
|
||||
numArray[1] = value[offset];
|
||||
numArray[2] = value[offset + 3];
|
||||
numArray[3] = value[offset + 2];
|
||||
numArray[4] = value[offset + 5];
|
||||
numArray[5] = value[offset + 4];
|
||||
numArray[6] = value[offset + 7];
|
||||
numArray[7] = value[offset + 6];
|
||||
break;
|
||||
|
||||
case DataFormatEnum.DCBA:
|
||||
numArray[0] = value[offset];
|
||||
numArray[1] = value[offset + 1];
|
||||
numArray[2] = value[offset + 2];
|
||||
numArray[3] = value[offset + 3];
|
||||
numArray[4] = value[offset + 4];
|
||||
numArray[5] = value[offset + 5];
|
||||
numArray[6] = value[offset + 6];
|
||||
numArray[7] = value[offset + 7];
|
||||
break;
|
||||
}
|
||||
return numArray;
|
||||
}
|
||||
|
||||
#endregion Tool
|
||||
|
||||
#region Tool
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private void ByteTransDataFormat4_Net6(ref byte value)
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
fixed (byte* p = &value)
|
||||
{
|
||||
var a = Unsafe.ReadUnaligned<byte>(p);
|
||||
var b = Unsafe.ReadUnaligned<byte>(p + 1);
|
||||
var c = Unsafe.ReadUnaligned<byte>(p + 2);
|
||||
var d = Unsafe.ReadUnaligned<byte>(p + 3);
|
||||
|
||||
switch (DataFormat)
|
||||
{
|
||||
case DataFormatEnum.ABCD:
|
||||
Unsafe.WriteUnaligned(p, d);
|
||||
Unsafe.WriteUnaligned(p + 1, c);
|
||||
Unsafe.WriteUnaligned(p + 2, b);
|
||||
Unsafe.WriteUnaligned(p + 3, a);
|
||||
break;
|
||||
|
||||
case DataFormatEnum.BADC:
|
||||
Unsafe.WriteUnaligned(p, c);
|
||||
Unsafe.WriteUnaligned(p + 1, d);
|
||||
Unsafe.WriteUnaligned(p + 2, a);
|
||||
Unsafe.WriteUnaligned(p + 3, b);
|
||||
break;
|
||||
|
||||
case DataFormatEnum.CDAB:
|
||||
Unsafe.WriteUnaligned(p, b);
|
||||
Unsafe.WriteUnaligned(p + 1, a);
|
||||
Unsafe.WriteUnaligned(p + 2, d);
|
||||
Unsafe.WriteUnaligned(p + 3, c);
|
||||
break;
|
||||
|
||||
case DataFormatEnum.DCBA:
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private void ByteTransDataFormat8_Net6(ref byte value)
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
fixed (byte* p = &value)
|
||||
{
|
||||
var a = Unsafe.ReadUnaligned<byte>(p);
|
||||
var b = Unsafe.ReadUnaligned<byte>(p + 1);
|
||||
var c = Unsafe.ReadUnaligned<byte>(p + 2);
|
||||
var d = Unsafe.ReadUnaligned<byte>(p + 3);
|
||||
var e = Unsafe.ReadUnaligned<byte>(p + 4);
|
||||
var f = Unsafe.ReadUnaligned<byte>(p + 5);
|
||||
var g = Unsafe.ReadUnaligned<byte>(p + 6);
|
||||
var h = Unsafe.ReadUnaligned<byte>(p + 7);
|
||||
|
||||
switch (DataFormat)
|
||||
{
|
||||
case DataFormatEnum.ABCD:
|
||||
Unsafe.WriteUnaligned(p, h);
|
||||
Unsafe.WriteUnaligned(p + 1, g);
|
||||
Unsafe.WriteUnaligned(p + 2, f);
|
||||
Unsafe.WriteUnaligned(p + 3, e);
|
||||
Unsafe.WriteUnaligned(p + 4, d);
|
||||
Unsafe.WriteUnaligned(p + 5, c);
|
||||
Unsafe.WriteUnaligned(p + 6, b);
|
||||
Unsafe.WriteUnaligned(p + 7, a);
|
||||
break;
|
||||
|
||||
case DataFormatEnum.BADC:
|
||||
Unsafe.WriteUnaligned(p, g);
|
||||
Unsafe.WriteUnaligned(p + 1, h);
|
||||
Unsafe.WriteUnaligned(p + 2, e);
|
||||
Unsafe.WriteUnaligned(p + 3, f);
|
||||
Unsafe.WriteUnaligned(p + 4, c);
|
||||
Unsafe.WriteUnaligned(p + 5, d);
|
||||
Unsafe.WriteUnaligned(p + 6, a);
|
||||
Unsafe.WriteUnaligned(p + 7, b);
|
||||
break;
|
||||
|
||||
case DataFormatEnum.CDAB:
|
||||
Unsafe.WriteUnaligned(p, b);
|
||||
Unsafe.WriteUnaligned(p + 1, a);
|
||||
Unsafe.WriteUnaligned(p + 2, d);
|
||||
Unsafe.WriteUnaligned(p + 3, c);
|
||||
Unsafe.WriteUnaligned(p + 4, f);
|
||||
Unsafe.WriteUnaligned(p + 5, e);
|
||||
Unsafe.WriteUnaligned(p + 6, h);
|
||||
Unsafe.WriteUnaligned(p + 7, g);
|
||||
break;
|
||||
|
||||
case DataFormatEnum.DCBA:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private void ByteTransDataFormat16_Net6(ref byte value)
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
fixed (byte* p = &value)
|
||||
{
|
||||
switch (DataFormat)
|
||||
{
|
||||
case DataFormatEnum.ABCD:
|
||||
var span = new Span<byte>(p, 16);
|
||||
span.Reverse();
|
||||
break;
|
||||
|
||||
case DataFormatEnum.DCBA:
|
||||
return;
|
||||
|
||||
default:
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion Tool
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user