修改:移入部分项目代码

This commit is contained in:
Diego
2024-10-14 19:06:09 +08:00
parent 1468297626
commit 1a116557a4
302 changed files with 12487 additions and 63 deletions

View File

@@ -10,8 +10,8 @@
// nuget动态加载的程序集
"SupportPackageNamePrefixs": [
"ThingsGateway.Foundation.Razor",
"ThingsGateway.Debug.Razor",
"ThingsGateway.Admin.Application",
"ThingsGateway.Admin.Razor",
"ThingsGateway.Core",
"ThingsGateway.Razor"

View File

@@ -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>

View File

@@ -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;
}
}

View File

@@ -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*100raw为原始值
/// </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;
}
}

View File

@@ -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>

View File

@@ -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);
}
}
}
}

View File

@@ -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>

View File

@@ -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();
}
}

View File

@@ -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>

View File

@@ -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);
}
}
}
}

View File

@@ -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>

View File

@@ -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; }
}

View File

@@ -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"
}
}

View File

@@ -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": "连接"
}
}

View File

@@ -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": "連接"
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}

View 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>();
}
}

View File

@@ -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>

View File

@@ -0,0 +1,9 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://thingsgateway.cn/
// QQ群605534569
//------------------------------------------------------------------------------

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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>

View File

@@ -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)
}
}
}
}

View File

@@ -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
{
}
}
}
}
}

View File

@@ -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;

View File

@@ -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>

View File

@@ -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);
}
}
}

View File

@@ -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;
}
}

View File

@@ -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; }
}

View File

@@ -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; }
}

View 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;
}
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}

View 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>>>
{
}

View File

@@ -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; }
}

View File

@@ -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);//如果本插件无法处理当前数据,请将数据转至下一个插件。
}
}

View File

@@ -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);//如果本插件无法处理当前数据,请将数据转至下一个插件。
}
}
}

View File

@@ -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; }
}

View File

@@ -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; }
}

View File

@@ -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; }
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View 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);
}
}

View 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);
}

View 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);
}
}

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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>());
}
}

View 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>
/// 采集返回消息
/// </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);
}

View File

@@ -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
{
}

View 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;
/// <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)
{
}
}

View File

@@ -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();
}
}
}

View File

@@ -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();
}
}
}

View File

@@ -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,
}

View 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,
}

View File

@@ -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,
}

View File

@@ -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,
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}
}

View 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>
/// 数据类型信息
/// </summary>
public static class DataTypeExtensions
{
/// <summary>
/// 获取实际字节长度不能确定返回0bool返回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),
};
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}
}

View File

@@ -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; // 返回指定属性的值
//}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -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
}

View File

@@ -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 };
}
}

View File

@@ -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;
}
}

View 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;

View File

@@ -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;
}
}
//使用频率高的多语言应初始化构建
}

View 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"
}
}

View 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": "寄存器地址"
}
}

View 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": "寄存器地址"
}
}

View File

@@ -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;
}
}
}

View 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);
}
}

View 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;
}
}
}

View File

@@ -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,
}

View File

@@ -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; }
}

View 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; }
}

View File

@@ -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();
}
}

View File

@@ -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");
}
}

View File

@@ -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) { }
}

View 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);
}

View 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 => { };
}
}

View File

@@ -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
}

View File

@@ -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));
}
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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>

View File

@@ -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);
}
}

View File

@@ -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
}

View File

@@ -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