Compare commits

..

15 Commits

Author SHA1 Message Date
2248356998 qq.com
20a2e3ff8e 示例 2025-08-25 20:39:26 +08:00
Diego
61a973b1b5 !73 10.11.10 2025-08-25 12:21:12 +00:00
2248356998 qq.com
cbd72e2081 添加文本日志配置json文件 2025-08-22 16:17:25 +08:00
2248356998 qq.com
4e0377b20c 添加文本日志配置json文件 2025-08-22 16:15:54 +08:00
2248356998 qq.com
fd318d3cdc 移除警告项 2025-08-22 16:12:14 +08:00
2248356998 qq.com
515bdb9700 10.11.7 2025-08-21 23:35:12 +08:00
2248356998 qq.com
46c1780017 10.11.6 2025-08-21 22:40:53 +08:00
2248356998 qq.com
fe78a4c3ca 更新依赖库 2025-08-21 22:05:30 +08:00
2248356998 qq.com
2d7effadf9 10.11.4 2025-08-21 21:13:18 +08:00
Diego
346c560f8b !72 更新依赖项 2025-08-21 11:46:58 +00:00
Diego
8e3bd89f61 修改编译项 2025-08-18 17:30:34 +08:00
Diego
6da142d080 10.10.23 2025-08-18 17:03:40 +08:00
2248356998 qq.com
ff7d029e6f 更新依赖 2025-08-14 20:25:00 +08:00
2248356998 qq.com
21b4695683 10.10.19 2025-08-14 05:47:54 +08:00
Diego
02ad494a26 !71 适配远程客户端 2025-08-12 16:00:53 +00:00
315 changed files with 9858 additions and 2849 deletions

1
.gitignore vendored
View File

@@ -364,6 +364,7 @@ FodyWeavers.xsd
/src/*Pro*/
/src/*Pro*
/src/**/*Pro*
/src/*pro*
/src/*pro*/
/src/ThingsGateway.Server/Configuration/GiteeOAuthSettings.json

View File

@@ -11,6 +11,7 @@
<IncludeBuildOutput>false</IncludeBuildOutput>
<!-- 避免 DLL 被打包到 lib/ -->
<EnableSourceGenerator>true</EnableSourceGenerator>
<!-- 可选 -->

View File

@@ -37,9 +37,8 @@ public class FileController : ControllerBase
var root = Directory.GetCurrentDirectory();
var wwwroot = Path.Combine(root, "wwwroot");
var filePath = Path.Combine(wwwroot, fileName);
// 防止路径穿越攻击
#pragma warning disable CA3003
if (!filePath.StartsWith(wwwroot, StringComparison.OrdinalIgnoreCase) || !System.IO.File.Exists(filePath))
if ((!(fileName.StartsWith(@"../Logs") || fileName.StartsWith(@"..\Logs")) && filePath.Contains("..")) || !System.IO.File.Exists(filePath))
{
return NotFound();
}
@@ -49,6 +48,6 @@ public class FileController : ControllerBase
Response.Headers.Append("Access-Control-Expose-Headers", "Content-Disposition");
return File(fileStream, "application/octet-stream", (fileName.Replace('/', '_')));
return File(fileStream, "application/octet-stream", (Path.GetFileName(filePath).Replace('/', '_')));
}
}

View File

@@ -21,16 +21,7 @@
"UserNoModule": "This account has not been assigned a module. Please contact the administrator",
"UserNull": "User {0} does not exist"
},
"ThingsGateway.Admin.Application.BaseDataEntity": {
"CreateOrgId": "CreateOrgId"
},
"ThingsGateway.Admin.Application.BaseEntity": {
"CreateTime": "CreateTime",
"CreateUser": "CreateUser",
"SortCode": "SortCode",
"UpdateTime": "UpdateTime",
"UpdateUser": "UpdateUser"
},
"ThingsGateway.Admin.Application.BlazorAuthenticationHandler": {
"UserExpire": "User expired, please login again"
},

View File

@@ -21,16 +21,7 @@
"UserNoModule": "该账号未分配模块,请联系管理员",
"UserNull": "用户 {0} 不存在"
},
"ThingsGateway.Admin.Application.BaseDataEntity": {
"CreateOrgId": "创建机构Id"
},
"ThingsGateway.Admin.Application.BaseEntity": {
"CreateTime": "创建时间",
"CreateUser": "创建人",
"SortCode": "排序",
"UpdateTime": "更新时间",
"UpdateUser": "更新人"
},
"ThingsGateway.Admin.Application.BlazorAuthenticationHandler": {
"UserExpire": "用户登录已过期,请重新登录"
},

View File

@@ -377,9 +377,9 @@ internal sealed class SysUserService : BaseService<SysUser>, ISysUserService
/// 获取用户拥有的资源
/// </summary>
/// <param name="id">用户id</param>
public async Task<GrantResourceData> OwnResourceAsync(long id)
public Task<GrantResourceData> OwnResourceAsync(long id)
{
return await _roleService.OwnResourceAsync(id, RelationCategoryEnum.UserHasResource).ConfigureAwait(false);
return _roleService.OwnResourceAsync(id, RelationCategoryEnum.UserHasResource);
}
/// <summary>
@@ -505,10 +505,10 @@ internal sealed class SysUserService : BaseService<SysUser>, ISysUserService
var password = await GetDefaultPassWord(true).ConfigureAwait(false);//获取默认密码,这里不走Aop所以需要加密一下
using var db = GetDB();
//重置密码
if (await db.UpdateSetColumnsTrueAsync<SysUser>(it => new SysUser
if ((await db.UpdateSetColumnsTrueAsync<SysUser>(it => new SysUser
{
Password = password
}, it => it.Id == id).ConfigureAwait(false))
}, it => it.Id == id).ConfigureAwait(false)) > 0)
{
DeleteUserFromCache(id);//从cache删除用户信息
var verificatInfoIds = _verificatInfoService.GetListByUserId(id);

View File

@@ -185,12 +185,12 @@ internal sealed class UserCenterService : BaseService<SysUser>, IUserCenterServi
using var db = GetDB();
//更新指定字段
var result = await db.UpdateSetColumnsTrueAsync<SysUser>(it => new SysUser
var result = (await db.UpdateSetColumnsTrueAsync<SysUser>(it => new SysUser
{
Email = input.Email,
Phone = input.Phone,
Avatar = input.Avatar,
}, it => it.Id == UserManager.UserId).ConfigureAwait(false);
}, it => it.Id == UserManager.UserId).ConfigureAwait(false)) > 0;
if (result)
_userService.DeleteUserFromCache(UserManager.UserId);//cache删除用户数据
}

View File

@@ -5,6 +5,7 @@
<PropertyGroup>
<GenerateDocumentationFile>True</GenerateDocumentationFile>
</PropertyGroup>
<PropertyGroup>
<TargetFrameworks>net8.0;net9.0;</TargetFrameworks>

View File

@@ -30,7 +30,7 @@ public class BlazorAppContext
/// <summary>
/// 全部菜单
/// </summary>
public IEnumerable<SysResource> AllMenus { get; private set; }
public List<SysResource> AllMenus { get; private set; }
/// <summary>
/// 当前用户
@@ -42,22 +42,22 @@ public class BlazorAppContext
/// <summary>
/// 用户个人菜单
/// </summary>
public IEnumerable<MenuItem> OwnMenuItems { get; private set; }
public List<MenuItem> OwnMenuItems { get; private set; }
/// <summary>
/// 不同模块的菜单
/// </summary>
public IEnumerable<MenuItem> AllOwnMenuItems { get; private set; }
public List<MenuItem> AllOwnMenuItems { get; private set; }
/// <summary>
/// 用户个人菜单,多个模块
/// </summary>
public IEnumerable<SysResource> OwnMenus { get; private set; }
public List<SysResource> OwnMenus { get; private set; }
/// <summary>
/// 用户个人菜单,非树形
/// </summary>
public IEnumerable<MenuItem> OwnSameLevelMenuItems { get; private set; }
public List<MenuItem> OwnSameLevelMenuItems { get; private set; }
/// <summary>
/// 个人工作台
@@ -67,9 +67,9 @@ public class BlazorAppContext
/// <summary>
/// 用户个人快捷方式菜单
/// </summary>
public IEnumerable<SysResource> UserWorkbenchOutputs { get; private set; }
public List<SysResource> UserWorkbenchOutputs { get; private set; }
public IEnumerable<SysResource> AllResource { get; private set; }
public List<SysResource> AllResource { get; private set; }
private ISysResourceService ResourceService { get; }
private ISysUserService SysUserService { get; }
@@ -93,7 +93,7 @@ public class BlazorAppContext
AllResource = sysResources;
var ids = CurrentUser.ModuleList.Select(a => a.Id).ToHashSet();
CurrentUser.ModuleList = AllResource.Where(a => ids.Contains(a.Id)).OrderBy(a => a.SortCode).ToList();
AllMenus = AllResource.Where(a => a.Category == ResourceCategoryEnum.Menu);
AllMenus = AllResource.Where(a => a.Category == ResourceCategoryEnum.Menu).ToList();
if (moduleId == null)
{
@@ -132,8 +132,8 @@ public class BlazorAppContext
Icon = item.Icon,
Url = item.Href,
Target = item.Target.ToString(),
});
UserWorkbenchOutputs = AllMenus.Where(it => UserWorkBench.Shortcuts.Contains(it.Id));
}).ToList();
UserWorkbenchOutputs = AllMenus.Where(it => UserWorkBench.Shortcuts.Contains(it.Id)).ToList();
}
}

View File

@@ -26,12 +26,12 @@
OnQueryAsync="OnQueryAsync" CustomerSearchModel="@CustomerSearchModel"
OnSaveAsync="Save" OnDeleteAsync="Delete">
<TableToolbarTemplate>
<PopConfirmButton Color=Color.Warning IsDisabled="SelectedRows.Count<=0||!AuthorizeButton(AdminOperConst.Add)" Text=@OperDescLocalizer["CopyResource"] Icon="fa fa-copy" OnConfirm="OnCopy">
<PopConfirmButton Color=Color.Warning IsKeepDisabled="SelectedRows.Count <= 0 || !AuthorizeButton(AdminOperConst.Add)" Text=@OperDescLocalizer["CopyResource"] Icon="fa fa-copy" OnConfirm="OnCopy">
<BodyTemplate>
<Select Items="ModuleSelectedItems" @bind-Value=CopyModule ShowLabel="false" />
</BodyTemplate>
</PopConfirmButton>
<PopConfirmButton Color=Color.Warning IsDisabled="SelectedRows.Count!=1||!AuthorizeButton(AdminOperConst.Edit)" Text=@OperDescLocalizer["ChangeParentResource"] Icon="fa fa-copy" OnConfirm="OnChangeParent">
<PopConfirmButton Color=Color.Warning IsKeepDisabled="SelectedRows.Count != 1 || !AuthorizeButton(AdminOperConst.Edit)" Text=@OperDescLocalizer["ChangeParentResource"] Icon="fa fa-copy" OnConfirm="OnChangeParent">
<BodyTemplate>
<div class="overflow-y-auto" style="height:500px">
<TreeView Items="MenuTreeItems" IsVirtualize="true" OnTreeItemClick="a=>{ChangeParentId=a.Value.Id;return Task.CompletedTask;}" />

View File

@@ -18,6 +18,7 @@
<PropertyGroup>
<TargetFrameworks>net8.0;net9.0</TargetFrameworks>
<!--<UseRazorSourceGenerator>false</UseRazorSourceGenerator>-->
</PropertyGroup>
<ItemGroup>

View File

@@ -29,7 +29,7 @@
<Target Name="AdminPostPublish" AfterTargets="Publish">
<ItemGroup>
<!-- setting up the variable for convenience -->
<AdminFiles Include="bin\$(Configuration)\$(TargetFramework)\SeedData\**" />
<AdminFiles Include="$(OutputPath)\$(TargetFramework)\SeedData\**" />
</ItemGroup>
<PropertyGroup>
</PropertyGroup>

View File

@@ -5,7 +5,8 @@
#推送docker push registry.cn-shenzhen.aliyuncs.com/thingsgateway/thingsgateway
#aspnetcore9.0环境
FROM mcr.microsoft.com/dotnet/aspnet:9.0 AS base
#FROM mcr.microsoft.com/dotnet/aspnet:9.0 AS base
FROM mcr.microsoft.com/dotnet/aspnet:8.0-noble AS base
COPY . /app
WORKDIR /app
#默认web
@@ -13,6 +14,8 @@ EXPOSE 5000
# 添加时区环境变量,亚洲,上海
ENV TimeZone=Asia/Shanghai
# 转发头
ENV ASPNETCORE_FORWARDEDHEADERS_ENABLED=true
# 使用软连接,并且将时区配置覆盖/etc/timezone
RUN ln -snf /usr/share/zoneinfo/$TimeZone /etc/localtime && echo $TimeZone > /etc/timezone

View File

@@ -5,7 +5,8 @@
#推送docker push registry.cn-shenzhen.aliyuncs.com/thingsgateway/thingsgateway_arm64
#aspnetcore9.0环境
FROM mcr.microsoft.com/dotnet/aspnet:9.0-alpine-arm64v8 AS base
#FROM mcr.microsoft.com/dotnet/aspnet:9.0-alpine-arm64v8 AS base
FROM mcr.microsoft.com/dotnet/aspnet:8.0-noble-arm64v8 AS base
COPY . /app
WORKDIR /app
#默认web
@@ -13,6 +14,8 @@ EXPOSE 5000
# 添加时区环境变量,亚洲,上海
ENV TimeZone=Asia/Shanghai
# 转发头
ENV ASPNETCORE_FORWARDEDHEADERS_ENABLED=true
# 使用软连接,并且将时区配置覆盖/etc/timezone
RUN ln -snf /usr/share/zoneinfo/$TimeZone /etc/localtime && echo $TimeZone > /etc/timezone

View File

@@ -5,6 +5,7 @@
<PropertyGroup>
<TargetFrameworks>net8.0;net9.0;</TargetFrameworks>
</PropertyGroup>
<!--<Import Project="Admin.targets" Condition=" '$(Configuration)' != 'Debug' " />-->

View File

@@ -5,6 +5,7 @@
<PropertyGroup>
<GenerateDocumentationFile>True</GenerateDocumentationFile>
</PropertyGroup>
<PropertyGroup>
<TargetFrameworks>net8.0;net9.0;</TargetFrameworks>
@@ -13,7 +14,7 @@
<ItemGroup>
<PackageReference Include="BootstrapBlazor.TableExport" Version="9.2.6" />
<PackageReference Include="Yitter.IdGenerator" Version="1.0.14" />
<PackageReference Include="BootstrapBlazor" Version="9.9.1" />
<PackageReference Include="BootstrapBlazor" Version="9.9.3" />
</ItemGroup>
<ItemGroup>

View File

@@ -209,16 +209,10 @@ public static class SqlSugarExtensions
}
/// <inheritdoc/>
public static async Task<bool> UpdateRangeAsync<T>(this SqlSugarClient db, List<T> updateObjs) where T : class, new()
public static Task<int> UpdateSetColumnsTrueAsync<T>(this SqlSugarClient db, Expression<Func<T, T>> columns, Expression<Func<T, bool>> whereExpression) where T : class, new()
{
return await db.Updateable(updateObjs).ExecuteCommandAsync().ConfigureAwait(false) > 0;
}
/// <inheritdoc/>
public static async Task<bool> UpdateSetColumnsTrueAsync<T>(this SqlSugarClient db, Expression<Func<T, T>> columns, Expression<Func<T, bool>> whereExpression) where T : class, new()
{
return await db.Updateable<T>().SetColumns(columns, appendColumnsByDataFilter: true).Where(whereExpression)
.ExecuteCommandAsync().ConfigureAwait(false) > 0;
return db.Updateable<T>().SetColumns(columns, appendColumnsByDataFilter: true).Where(whereExpression)
.ExecuteCommandAsync();
}
private static IEnumerable<T> Sort<T>(this IEnumerable<T> list, BasePageInput basePageInput)

View File

@@ -0,0 +1,13 @@
{
"ThingsGateway.Admin.Application.BaseDataEntity": {
"CreateOrgId": "CreateOrgId"
},
"ThingsGateway.Admin.Application.BaseEntity": {
"CreateTime": "CreateTime",
"CreateUser": "CreateUser",
"SortCode": "SortCode",
"UpdateTime": "UpdateTime",
"UpdateUser": "UpdateUser"
}
}

View File

@@ -0,0 +1,13 @@
{
"ThingsGateway.DB.BaseDataEntity": {
"CreateOrgId": "创建机构Id"
},
"ThingsGateway.DB.BaseEntity": {
"CreateTime": "创建时间",
"CreateUser": "创建人",
"SortCode": "排序",
"UpdateTime": "更新时间",
"UpdateUser": "更新人"
}
}

View File

@@ -5,6 +5,7 @@
<PropertyGroup>
<GenerateDocumentationFile>True</GenerateDocumentationFile>
</PropertyGroup>
<PropertyGroup>
<TargetFrameworks>net8.0;net9.0;</TargetFrameworks>
@@ -18,6 +19,12 @@
<None Remove="..\..\..\README.zh-CN.md" Pack="false" PackagePath="\" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Locales\en-US.json" />
<EmbeddedResource Include="Locales\zh-CN.json" />
</ItemGroup>
<ItemGroup>
<!--<PackageReference Include="ThingsGateway.Razor" Version="$(SourceGeneratorVersion)" />-->
<!--<ProjectReference Include="..\ThingsGateway.Razor\ThingsGateway.Razor.csproj" />-->

View File

@@ -554,10 +554,9 @@ public static class App
{
types = ass.GetTypes();
}
catch
catch (Exception ex)
{
XTrace.Log.Warn($"Error load `{ass.FullName}` assembly.");
Console.WriteLine($"Error load `{ass.FullName}` assembly.");
XTrace.Log.Warn($"Error load `{ass.FullName}` assembly. : {ex.Message}");
}
return types.Where(u => u.IsPublic && !u.IsDefined(typeof(SuppressSnifferAttribute), false));

View File

@@ -5,6 +5,7 @@
<PropertyGroup>
<TargetFrameworks>net8.0;net9.0;</TargetFrameworks>
</PropertyGroup>
<PropertyGroup>

View File

@@ -0,0 +1,45 @@
namespace ThingsGateway.NewLife;
using System;
using System.Collections;
using System.Collections.Generic;
public class BoundedQueue<T> : IEnumerable<T>
{
private readonly Queue<T> _queue;
private readonly int _capacity;
private readonly object _syncRoot = new object();
public BoundedQueue(int capacity)
{
if (capacity <= 0) throw new ArgumentOutOfRangeException(nameof(capacity));
_capacity = capacity;
_queue = new Queue<T>(capacity);
}
public void Enqueue(T item)
{
lock (_syncRoot)
{
if (_queue.Count == _capacity)
_queue.Dequeue();
_queue.Enqueue(item);
}
}
public int Count
{
get { lock (_syncRoot) return _queue.Count; }
}
public IEnumerator<T> GetEnumerator()
{
lock (_syncRoot)
{
return new List<T>(_queue).GetEnumerator();
}
}
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}

View File

@@ -21,6 +21,7 @@ namespace ThingsGateway.NewLife.Json.Extension;
/// </summary>
public static class SystemTextJsonExtension
{
/// <summary>
/// 默认Json规则带缩进
/// </summary>
@@ -31,37 +32,51 @@ public static class SystemTextJsonExtension
/// </summary>
public static JsonSerializerOptions NoneIndentedOptions;
/// <summary>
/// 默认Json规则带缩进
/// </summary>
public static JsonSerializerOptions IgnoreNullIndentedOptions;
/// <summary>
/// 默认Json规则无缩进
/// </summary>
public static JsonSerializerOptions IgnoreNullNoneIndentedOptions;
public static JsonSerializerOptions GetOptions(bool writeIndented, bool ignoreNull)
{
var options = new JsonSerializerOptions
{
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
WriteIndented = writeIndented,
DefaultIgnoreCondition = ignoreNull
? JsonIgnoreCondition.WhenWritingNull
: JsonIgnoreCondition.Never,
NumberHandling = JsonNumberHandling.AllowNamedFloatingPointLiterals,
};
options.Converters.Add(new ByteArrayToNumberArrayConverterSystemTextJson());
options.Converters.Add(new JTokenSystemTextJsonConverter());
options.Converters.Add(new JValueSystemTextJsonConverter());
options.Converters.Add(new JObjectSystemTextJsonConverter());
options.Converters.Add(new JArraySystemTextJsonConverter());
return options;
}
static SystemTextJsonExtension()
{
IndentedOptions = new JsonSerializerOptions
{
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
WriteIndented = true, // 缩进
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, // 忽略 null
NumberHandling = JsonNumberHandling.AllowNamedFloatingPointLiterals,
};
// 如有自定义Converter这里添加
// IndentedOptions.Converters.Add(new ByteArrayJsonConverter());
IndentedOptions.Converters.Add(new ByteArrayToNumberArrayConverterSystemTextJson());
IndentedOptions.Converters.Add(new JTokenSystemTextJsonConverter());
IndentedOptions.Converters.Add(new JValueSystemTextJsonConverter());
IndentedOptions.Converters.Add(new JObjectSystemTextJsonConverter());
IndentedOptions.Converters.Add(new JArraySystemTextJsonConverter());
NoneIndentedOptions = new JsonSerializerOptions
{
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
WriteIndented = false, // 不缩进
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
NumberHandling = JsonNumberHandling.AllowNamedFloatingPointLiterals,
};
NoneIndentedOptions.Converters.Add(new ByteArrayToNumberArrayConverterSystemTextJson());
NoneIndentedOptions.Converters.Add(new JTokenSystemTextJsonConverter());
NoneIndentedOptions.Converters.Add(new JValueSystemTextJsonConverter());
NoneIndentedOptions.Converters.Add(new JObjectSystemTextJsonConverter());
NoneIndentedOptions.Converters.Add(new JArraySystemTextJsonConverter());
// NoneIndentedOptions.Converters.Add(new ByteArrayJsonConverter());
IndentedOptions = GetOptions(true, false);
NoneIndentedOptions = GetOptions(false, false);
IgnoreNullIndentedOptions = GetOptions(true, true);
IgnoreNullNoneIndentedOptions = GetOptions(false, true);
}
/// <summary>
/// 反序列化
/// </summary>
@@ -96,17 +111,17 @@ public static class SystemTextJsonExtension
/// <summary>
/// 序列化
/// </summary>
public static string ToSystemTextJsonString(this object item, bool indented = true)
public static string ToSystemTextJsonString(this object item, bool indented = true, bool ignoreNull = true)
{
return JsonSerializer.Serialize(item, item?.GetType() ?? typeof(object), indented ? IndentedOptions : NoneIndentedOptions);
return JsonSerializer.Serialize(item, item?.GetType() ?? typeof(object), ignoreNull ? indented ? IgnoreNullIndentedOptions : IgnoreNullNoneIndentedOptions : indented ? IndentedOptions : NoneIndentedOptions);
}
/// <summary>
/// 序列化
/// </summary>
public static byte[] ToSystemTextJsonUtf8Bytes(this object item, bool indented = true)
public static byte[] ToSystemTextJsonUtf8Bytes(this object item, bool indented = true, bool ignoreNull = true)
{
return JsonSerializer.SerializeToUtf8Bytes(item, item.GetType(), indented ? IndentedOptions : NoneIndentedOptions);
return JsonSerializer.SerializeToUtf8Bytes(item, item.GetType(), ignoreNull ? indented ? IgnoreNullIndentedOptions : IgnoreNullNoneIndentedOptions : indented ? IndentedOptions : NoneIndentedOptions);
}
}

View File

@@ -190,7 +190,31 @@ public sealed class Crc32 //: HashAlgorithm
crc.Update(stream, count);
return crc.Value;
}
/// <summary>
/// 添加Sequence进行校验
/// </summary>
/// <param name="sequence"></param>
/// <returns></returns>
public Crc32 Update(ReadOnlySequence<byte> sequence)
{
foreach (var segment in sequence)
{
Update(segment.Span);
}
return this;
}
/// <summary>
/// 计算校验码 (Sequence)
/// </summary>
/// <param name="sequence"></param>
/// <returns></returns>
public static UInt32 Compute(ReadOnlySequence<byte> sequence)
{
var crc = new Crc32();
crc.Update(sequence);
return crc.Value;
}
//#region 抽象实现
///// <summary>哈希核心</summary>
///// <param name="array"></param>

View File

@@ -12,6 +12,7 @@
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<SignAssembly>True</SignAssembly>
<AssemblyOriginatorKeyFile>newlife.snk</AssemblyOriginatorKeyFile>
</PropertyGroup>

View File

@@ -4,6 +4,7 @@
<PropertyGroup>
<TargetFrameworks>net8.0;net9.0;</TargetFrameworks>
</PropertyGroup>
<ItemGroup>

View File

@@ -41,7 +41,7 @@
}
<PopConfirmButton IsAsync IsDisabled=@_importPreviews.Any(it => it.Value.HasError) Color=Color.Warning class="mt-2" OnConfirm=@(SaveDeviceImport)>@Localizer["Import"]</PopConfirmButton>
<PopConfirmButton IsAsync IsKeepDisabled=@_importPreviews.Any(it => it.Value.HasError) Color=Color.Warning class="mt-2" OnConfirm=@(SaveDeviceImport)>@Localizer["Import"]</PopConfirmButton>
@*
<Button IsAsync class="mt-2" IsDisabled=@_importPreviews.Any(it => it.Value.HasError) OnClick="() => step.Next()">@Localizer["Next"]</Button> *@

View File

@@ -28,7 +28,7 @@ public partial class ImportExcelConfirm
[Inject]
[NotNull]
private IStringLocalizer<ImportExcel>? Localizer { get; set; }
private IStringLocalizer<ImportExcelConfirm>? Localizer { get; set; }
/// <summary>
/// 预览

View File

@@ -25,7 +25,8 @@
"Success": "Success",
"TablesExportButtonExcelText": "Export Excel",
"TablesImportButtonExcelText": "Import Excel",
"True": "Yes"
"True": "Yes",
"Info": "Info"
},
"ThingsGateway.Razor.About": {
"Community": "Community",
@@ -71,6 +72,19 @@
"Validate": "Validate",
"ValidateText": "Validation Content"
},
"ThingsGateway.Razor.ImportExcelConfirm": {
"First": "Step 1",
"Import": "Import",
"Next": "Next",
"Reset": "Reset",
"Second": "Step 2",
"Third": "Step 3",
"Tip": "When the data volume is large (more than 200,000), the import may take more than 1 minute, please be patient",
"Upload": "Upload File",
"UploadCount": " Table {0}, import {1} records",
"Validate": "Validate",
"ValidateText": "Validation Content"
},
"ThingsGateway.Razor.MenuIconList": {
"ChoiceIcon": "Icon for Selection"
}

View File

@@ -25,7 +25,8 @@
"Success": "成功",
"TablesExportButtonExcelText": "导出Excel",
"TablesImportButtonExcelText": "导入Excel",
"True": "是"
"True": "是",
"Info": "详情"
},
"ThingsGateway.Razor.About": {
"Community": "社区",
@@ -71,6 +72,19 @@
"Validate": "验证",
"ValidateText": "验证内容"
},
"ThingsGateway.Razor.ImportExcelConfirm": {
"First": "第一步",
"Import": "导入",
"Next": "下一步",
"Reset": "重置",
"Second": "第二步",
"Third": "第三",
"Tip": "数据量较大时(大于20万)所需导入时间可能超过1分钟请耐心等待",
"Upload": "上传文件",
"UploadCount": " 表 {0},导入 {1} 条数据",
"Validate": "验证",
"ValidateText": "验证内容"
},
"ThingsGateway.Razor.MenuIconList": {
"ChoiceIcon": "选择图标"
}

View File

@@ -3,6 +3,7 @@
<Import Project="..\..\PackNuget.props" />
<PropertyGroup>
<TargetFrameworks>net8.0</TargetFrameworks>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="BootstrapBlazor.FontAwesome" Version="9.1.0" />

View File

@@ -132,35 +132,43 @@ namespace ThingsGateway.SqlSugar
{
// 准备多部分表单数据
var boundary = "---------------" + DateTime.Now.Ticks.ToString("x");
var list = new List<Hashtable>();
tableName ??= db.EntityMaintenance.GetEntityInfo<T>().DbTableName;
// 获取或创建列信息缓存
var key = "QuestDbBulkCopy" + typeof(T).FullName + typeof(T).GetHashCode();
var columns = ReflectionInoCacheService.Instance.GetOrCreate(key, () =>
db.CopyNew().DbMaintenance.GetColumnInfosByTableName(tableName));
// 构建schema信息
columns.ForEach(d =>
var list = ReflectionInoCacheService.Instance.GetOrCreate($"{key}{dateFormat}List<Hashtable>", () =>
{
if (d.DataType == "TIMESTAMP")
var list = new List<Hashtable>();
// 构建schema信息
columns.ForEach(d =>
{
list.Add(new Hashtable()
if (d.DataType == "TIMESTAMP")
{
list.Add(new Hashtable()
{
{ "name", d.DbColumnName },
{ "type", d.DataType },
{ "pattern", dateFormat}
});
}
else
{
list.Add(new Hashtable()
}
else
{
list.Add(new Hashtable()
{
{ "name", d.DbColumnName },
{ "type", d.DataType }
});
}
});
}
});
return list;
}
);
var schema = JsonConvert.SerializeObject(list);
// 写入CSV文件

View File

@@ -5,6 +5,7 @@
<PropertyGroup>
<GenerateDocumentationFile>True</GenerateDocumentationFile>
</PropertyGroup>
<PropertyGroup>
<TargetFrameworks>net8.0;net9.0;</TargetFrameworks>
@@ -22,18 +23,18 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="SqlSugarCore.Dm" Version="8.8.0" />
<PackageReference Include="SqlSugarCore.Kdbndp" Version="9.3.7.728" />
<PackageReference Include="SqlSugarCore.Dm" Version="8.8.1" />
<PackageReference Include="SqlSugarCore.Kdbndp" Version="9.3.7.821" />
<PackageReference Include="Microsoft.Data.Sqlite" Version="7.0.20" />
<!--<PackageReference Include="Microsoft.Data.Sqlite" Version="$(NET9Version)" />-->
<PackageReference Include="MySqlConnector" Version="2.4.0" />
<PackageReference Include="Npgsql" Version="9.0.3" />
<PackageReference Include="CsvHelper" Version="33.1.0" />
<PackageReference Include="TDengine.Connector" Version="3.1.7" />
<PackageReference Include="TDengine.Connector" Version="3.1.8" />
<PackageReference Include="Oracle.ManagedDataAccess.Core" Version="23.9.1" />
<PackageReference Include="Oscar.Data.SqlClient" Version="4.2.21" />
<PackageReference Include="Oscar.Data.SqlClient" Version="4.2.23" />
<PackageReference Include="System.Data.Common" Version="4.3.0" />
<PackageReference Include="Microsoft.Data.SqlClient" Version="6.1.0" />
<PackageReference Include="Microsoft.Data.SqlClient" Version="6.1.1" />
<PackageReference Include="System.Reflection.Emit.Lightweight" Version="4.7.0" />
<PackageReference Include="System.Text.RegularExpressions" Version="4.3.1" />
<PackageReference Include="System.Formats.Asn1" Version="8.0.2" />

View File

@@ -1,11 +1,11 @@
<Project>
<PropertyGroup>
<PluginVersion>10.10.16</PluginVersion>
<ProPluginVersion>10.10.16</ProPluginVersion>
<DefaultVersion>10.10.16</DefaultVersion>
<AuthenticationVersion>10.10.1</AuthenticationVersion>
<SourceGeneratorVersion>10.10.1</SourceGeneratorVersion>
<PluginVersion>10.11.10</PluginVersion>
<ProPluginVersion>10.11.10</ProPluginVersion>
<DefaultVersion>10.11.10</DefaultVersion>
<AuthenticationVersion>10.11.2</AuthenticationVersion>
<SourceGeneratorVersion>10.11.2</SourceGeneratorVersion>
<NET8Version>8.0.19</NET8Version>
<NET9Version>9.0.8</NET9Version>
<SatelliteResourceLanguages>zh-Hans;en-US</SatelliteResourceLanguages>
@@ -28,7 +28,8 @@
<AnalysisModeStyle>None</AnalysisModeStyle>
<NoWarn>
CS8603;CS8618;CS1591;CS8625;CS8602;CS8604;CS8600;CS8601;CS8714;CS8619;CS8629;CS8765;CS8634;CS8621;CS8767;CS8633;CS8620;CS8610;CS8631;CS8605;CS8622;CS8613;NU5100;NU5104;NU1903;NU1902;CA1863;CA1812;CA1805;CA1515;CA1508;CA1819;CA1852;CA5394;CA1822;CA1815;CA1813;CA2000;CA5358;CA5384;CA5400;CA5401;CA1814;CA1835;CA5392;CA5350;CA2100;CA1848;CA1810;CA1513;CA5351;CA1510;CA1512;CA1823;NETSDK1206
CS8603;CS8618;CS1591;CS8625;CS8602;CS8604;CS8600;CS8601;CS8714;CS8619;CS8629;CS8765;CS8634;CS8621;CS8767;CS8633;CS8620;CS8610;CS8631;CS8605;CS8622;CS8613;NU5100;NU5104;NU1903;NU1902;CA1863;CA1812;CA1805;CA1515;CA1508;CA1819;CA1852;CA5394;CA1822;CA1815;CA1813;CA2000;CA5358;CA5384;CA5400;CA5401;CA1814;CA1835;CA5392;CA5350;CA2100;CA1848;CA1810;CA1513;CA5351;CA1510;CA1512;CA1823;RCS1102;RCS1194;NETSDK1206
</NoWarn>
<TargetFrameworks>net8.0;</TargetFrameworks>
<LangVersion>13.0</LangVersion>

File diff suppressed because it is too large Load Diff

Binary file not shown.

View File

@@ -4,10 +4,11 @@
<PropertyGroup>
<TargetFrameworks>netstandard2.0;</TargetFrameworks>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="CS-Script" Version="4.10.1" />
<PackageReference Include="CS-Script" Version="4.11.0" />
</ItemGroup>
<ItemGroup>

View File

@@ -89,7 +89,7 @@ public partial class ChannelComponent : ComponentBase
await Channel.SetupAsync(config);
}
await Channel.ConnectAsync(Channel.ChannelOptions.ConnectTimeout, default);
await Channel.ConnectAsync(default);
if (OnConnectClick.HasDelegate)
await OnConnectClick.InvokeAsync(Channel);

View File

@@ -23,7 +23,7 @@
<EditorItem @bind-Field=Model.EncodingName>
<EditTemplate Context="value">
<div class="col-12 col-sm-4">
<Select @bind-Value=value.EncodingName Items="EncodingItems" />
<Select @bind-Value=value.EncodingName Items="EncodingItems" IsClearable/>
</div>
</EditTemplate>
</EditorItem>

View File

@@ -31,6 +31,6 @@ public partial class ConverterConfigComponent : ComponentBase
protected override void OnInitialized()
{
BoolItems = LocalizerUtil.GetBoolItems(Model.GetType(), nameof(Model.VariableStringLength), true);
EncodingItems = new List<SelectedItem>() { new SelectedItem("", "none") }.Concat(Encoding.GetEncodings().Select(a => new SelectedItem(a.CodePage.ToString(), a.DisplayName))).ToList();
EncodingItems = Encoding.GetEncodings().Select(a => new SelectedItem(a.CodePage.ToString(), a.DisplayName)).ToList();
}
}

View File

@@ -8,7 +8,7 @@
// QQ群605534569
//------------------------------------------------------------------------------
namespace ThingsGateway.Gateway.Razor;
namespace ThingsGateway.Debug;
public class ValueTransformConfig
{

View File

@@ -1,9 +1,5 @@
@namespace ThingsGateway.Gateway.Razor
@using ThingsGateway.Admin.Application
@using ThingsGateway.Admin.Razor
@namespace ThingsGateway.Debug
@using ThingsGateway.Foundation
@using ThingsGateway.Gateway.Application
@inherits ComponentDefault
<ValidateForm class="p-4 h-100" Model="@ValueTransformConfig" OnValidSubmit="OnSave">
<EditorForm AutoGenerateAllItem="false" RowType=RowType.Inline ItemsPerRow=1 LabelWidth=150 Model="ValueTransformConfig">

View File

@@ -14,7 +14,7 @@ using System.Text.RegularExpressions;
using ThingsGateway.NewLife.Extension;
namespace ThingsGateway.Gateway.Razor;
namespace ThingsGateway.Debug;
public partial class ValueTransformConfigPage
{
@@ -205,5 +205,10 @@ public partial class ValueTransformConfigPage
}
}
[Inject]
ToastService ToastService { get; set; }
[Inject]
IStringLocalizer<ThingsGateway.Razor._Imports> RazorLocalizer { get; set; }
#endregion
}

View File

@@ -1,4 +1,23 @@
{
"ThingsGateway.Debug.ValueTransformType": {
"None": "None",
"Linear": "Linear",
"Sqrt": "Sqrt"
},
"ThingsGateway.Debug.ValueTransformConfig": {
"TransformType": "TransformType",
"MinMax": "MinMax",
"ClampToRawRange": "ClampToRawRange",
"DecimalPlaces": "DecimalPlaces",
"RawMin": "RawMin",
"RawMax": "RawMax",
"ActualMin": "ActualMin",
"ActualMax": "ActualMax"
},
"ThingsGateway.Debug.ChannelComponent": {
"BaudRate": "Baud Rate",
"BindUrl": "Local Bind IP Address",

View File

@@ -1,4 +1,21 @@
{
"ThingsGateway.Debug.ValueTransformType": {
"None": "无",
"Linear": "线性",
"Sqrt": "开方"
},
"ThingsGateway.Debug.ValueTransformConfig": {
"TransformType": "转换方式",
"MinMax": "最小最大值",
"ClampToRawRange": "限制范围",
"DecimalPlaces": "保留小数位",
"RawMin": "原始最小值",
"RawMax": "原始最大值",
"ActualMin": "实际最小值",
"ActualMax": "实际最大值"
},
"ThingsGateway.Debug.ChannelComponent": {
"BaudRate": "波特率",
"BindUrl": "本地url",

View File

@@ -40,7 +40,7 @@ public class PlatformService : IPlatformService
await using var jSObject = await JSRuntime.InvokeAsync<IJSObjectReference>("import", $"{WebsiteConst.DefaultResourceUrl}js/downloadFile.js");
var path = Path.GetRelativePath("wwwroot", item);
string fileName = DateTime.Now.ToFileDateTimeFormat();
await jSObject.InvokeVoidAsync("blazor_downloadFile", url, fileName, new { FileName = path });
await jSObject.InvokeAsync<bool>("blazor_downloadFile", url, fileName, new { FileName = path });
}
}
}

View File

@@ -4,6 +4,7 @@
<Import Project="..\..\PackNuget.props" />
<PropertyGroup>
<TargetFrameworks>net8.0;</TargetFrameworks>
<!--<UseRazorSourceGenerator>false</UseRazorSourceGenerator>-->
</PropertyGroup>
<ItemGroup>

View File

@@ -5,6 +5,7 @@
<EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
<NoPackageAnalysis>true</NoPackageAnalysis>
<SignAssembly>false</SignAssembly>
</PropertyGroup>
<ItemGroup>

View File

@@ -5,6 +5,7 @@
<PropertyGroup>
<TargetFrameworks>netstandard2.0;</TargetFrameworks>
<Version>$(SourceGeneratorVersion)</Version>
</PropertyGroup>
<ItemGroup>

View File

@@ -8,6 +8,8 @@
// QQ群605534569
//------------------------------------------------------------------------------
using System.Buffers;
namespace ThingsGateway.Foundation;
/// <summary>
@@ -16,12 +18,12 @@ namespace ThingsGateway.Foundation;
public abstract class DDPMessage : MessageBase, IResultMessage
{
/// <inheritdoc/>
public override int HeaderLength => 4;
public override long HeaderLength => 4;
public byte Type = 0;
public string Id;
public override FilterResult CheckBody<TByteBlock>(ref TByteBlock byteBlock)
{
Id = byteBlock.ToString(byteBlock.Position, 11).Replace("\0", "");
Id = byteBlock.ToString(byteBlock.BytesRead, 11).Replace("\0", "");
OperCode = 0;
Content = GetContent(ref byteBlock);
@@ -44,31 +46,31 @@ public abstract class DDPMessage : MessageBase, IResultMessage
}
}
public abstract int GetBodyLength<TByteBlock>(ref TByteBlock byteBlock) where TByteBlock : IByteBlockReader;
public abstract byte[] GetContent<TByteBlock>(ref TByteBlock byteBlock) where TByteBlock : IByteBlockReader;
public abstract long GetBodyLength<TByteBlock>(ref TByteBlock byteBlock) where TByteBlock : IBytesReader;
public abstract byte[] GetContent<TByteBlock>(ref TByteBlock byteBlock) where TByteBlock : IBytesReader;
}
public class DDPTcpMessage : DDPMessage
{
public override int GetBodyLength<TByteBlock>(ref TByteBlock byteBlock)
public override long GetBodyLength<TByteBlock>(ref TByteBlock byteBlock)
{
return ReaderExtension.ReadValue<TByteBlock, ushort>(ref byteBlock, EndianType.Big) - 4;
}
public override byte[] GetContent<TByteBlock>(ref TByteBlock byteBlock)
{
return byteBlock.Span.Slice(byteBlock.Position + 11, BodyLength - 12).ToArray();
return byteBlock.TotalSequence.Slice(byteBlock.BytesRead + 11, BodyLength - 12).ToArray();
}
}
public class DDPUdpMessage : DDPMessage
{
public override int GetBodyLength<TByteBlock>(ref TByteBlock byteBlock)
public override long GetBodyLength<TByteBlock>(ref TByteBlock byteBlock)
{
return byteBlock.Length - 4;
return (byteBlock.BytesRead + byteBlock.BytesRemaining - 4);
}
public override byte[] GetContent<TByteBlock>(ref TByteBlock byteBlock)
{
return byteBlock.Span.Slice(byteBlock.Position + 12, BodyLength - 12).ToArray();
return byteBlock.TotalSequence.Slice(byteBlock.BytesRead + 12, BodyLength - 12).ToArray();
}
}

View File

@@ -25,6 +25,7 @@ public class DDPSend : ISendMessage
string Id;
byte Command;
bool Tcp;
public DDPSend(ReadOnlyMemory<byte> readOnlyMemory, string id, bool tcp, byte command = 0x89)
{
Tcp = tcp;
@@ -32,7 +33,8 @@ public class DDPSend : ISendMessage
Id = id;
Command = command;
}
public void Build<TByteBlock>(ref TByteBlock byteBlock) where TByteBlock : IByteBlockWriter
public void Build<TByteBlock>(ref TByteBlock byteBlock) where TByteBlock : IBytesWriter
{
WriterExtension.WriteValue(ref byteBlock, (byte)0x7b);
WriterExtension.WriteValue(ref byteBlock, (byte)Command);

View File

@@ -10,8 +10,6 @@
using System.Runtime.CompilerServices;
using ThingsGateway.NewLife;
using TouchSocket.Resources;
namespace ThingsGateway.Foundation;
@@ -33,50 +31,120 @@ public class DDPTcpSessionClientChannel : TcpSessionClientChannel
DDPAdapter.Config(Config);
}
// 将当前实例的日志记录器和加载回调设置到适配器中
DDPAdapter.Logger = Logger;
DDPAdapter.OnLoaded(this);
DDPAdapter.SendAsyncCallBack = DDPSendAsync;
DDPAdapter.ReceivedAsyncCallBack = DDPHandleReceivedData;
DataHandlingAdapter.SendAsyncCallBack = DefaultSendAsync;
return base.OnTcpConnected(e);
}
protected Task DefaultSendAsync(ReadOnlyMemory<byte> memory, CancellationToken cancellationToken)
{
return DDPAdapter.SendInputAsync(new DDPSend(memory, Id, true), cancellationToken);
}
protected Task DDPSendAsync(ReadOnlyMemory<byte> memory, CancellationToken cancellationToken)
{
return base.ProtectedDefaultSendAsync(memory, cancellationToken);
}
private DDPMessage DDPMessage { get; set; }
private Task DDPHandleReceivedData(IByteBlockReader byteBlock, IRequestInfo requestInfo)
#region
/// <summary>
/// 异步发送数据,通过适配器模式灵活处理数据发送。
/// </summary>
/// <param name="memory">待发送的只读字节内存块。</param>
/// <param name="token">可取消令箭</param>
/// <returns>一个异步任务,表示发送操作。</returns>
protected virtual async Task NewProtectedSendAsync(ReadOnlyMemory<byte> memory, CancellationToken token)
{
if (requestInfo is DDPMessage dDPMessage)
DDPMessage = dDPMessage;
this.ThrowIfDisposed();
this.ThrowIfClientNotConnected();
return EasyTask.CompletedTask;
}
private DeviceSingleStreamDataHandleAdapter<DDPTcpMessage> DDPAdapter = new();
private WaitLock _waitLock = new(nameof(DDPTcpSessionClientChannel));
if (!await this.OnTcpSending(memory).ConfigureAwait(false)) return;
protected override async ValueTask<bool> OnTcpReceiving(IByteBlockReader byteBlock)
{
DDPMessage? message = null;
var transport = this.Transport;
var adapter = this.DataHandlingAdapter;
var locker = transport.SemaphoreSlimForWriter;
await locker.WaitAsync(token).ConfigureAwait(false);
try
{
await _waitLock.WaitAsync().ConfigureAwait(false);
await DDPAdapter.ReceivedInputAsync(byteBlock).ConfigureAwait(false);
message = DDPMessage;
DDPMessage = null;
// 如果数据处理适配器未设置,则使用默认发送方式。
if (adapter == null)
{
await transport.Output.WriteAsync(memory, token).ConfigureAwait(false);
}
else
{
var byteBlock = new ByteBlock(1024);
var ddpSend = new DDPSend(memory, Id, true);
ddpSend.Build(ref byteBlock);
var newMemory = byteBlock.Memory;
var writer = new PipeBytesWriter(transport.Output);
adapter.SendInput(ref writer, in newMemory);
await writer.FlushAsync(token).ConfigureAwait(false);
}
}
finally
{
_waitLock.Release();
locker.Release();
}
}
/// <summary>
/// 异步发送请求信息的受保护方法。
///
/// 此方法首先检查当前对象是否能够发送请求信息,如果不能,则抛出异常。
/// 如果可以发送,它将使用数据处理适配器来异步发送输入请求。
/// </summary>
/// <param name="requestInfo">要发送的请求信息。</param>
/// <param name="token">可取消令箭</param>
/// <returns>返回一个任务,该任务代表异步操作的结果。</returns>
protected virtual async Task NewProtectedSendAsync(IRequestInfo requestInfo, CancellationToken token)
{
// 检查是否具备发送请求的条件,如果不具备则抛出异常
this.ThrowIfCannotSendRequestInfo();
this.ThrowIfDisposed();
this.ThrowIfClientNotConnected();
var transport = this.Transport;
var adapter = this.DataHandlingAdapter;
var locker = transport.SemaphoreSlimForWriter;
await locker.WaitAsync(token).ConfigureAwait(false);
try
{
var byteBlock = new ByteBlock(1024);
if (requestInfo is not IRequestInfoBuilder requestInfoBuilder)
{
throw new Exception();
}
requestInfoBuilder.Build(ref byteBlock);
var ddpSend = new DDPSend(byteBlock.Memory, Id, true);
var writer = new PipeBytesWriter(transport.Output);
adapter.SendInput(ref writer, ddpSend);
await writer.FlushAsync(token).ConfigureAwait(false);
}
finally
{
locker.Release();
}
}
#endregion
public override Task SendAsync(IRequestInfo requestInfo, CancellationToken token = default)
{
return NewProtectedSendAsync(requestInfo, token);
}
public override Task SendAsync(ReadOnlyMemory<byte> memory, CancellationToken token = default)
{
return NewProtectedSendAsync(memory, token);
}
private DeviceSingleStreamDataHandleAdapter<DDPTcpMessage> DDPAdapter = new();
protected override async ValueTask<bool> OnTcpReceiving(IBytesReader byteBlock)
{
if (DDPAdapter.TryParseRequest(ref byteBlock, out var message))
{
return true;
}
if (message != null)
@@ -90,11 +158,11 @@ public class DDPTcpSessionClientChannel : TcpSessionClientChannel
if (this.DataHandlingAdapter == null)
{
await this.OnTcpReceived(new ReceivedDataEventArgs(reader, default)).ConfigureAwait(EasyTask.ContinueOnCapturedContext);
await this.OnTcpReceived(new ReceivedDataEventArgs(message.Content, default)).ConfigureAwait(false);
}
else
{
await this.DataHandlingAdapter.ReceivedInputAsync(reader).ConfigureAwait(EasyTask.ContinueOnCapturedContext);
await this.DataHandlingAdapter.ReceivedInputAsync(reader).ConfigureAwait(false);
}
return true;
@@ -127,16 +195,16 @@ public class DDPTcpSessionClientChannel : TcpSessionClientChannel
}
}
await ResetIdAsync(id).ConfigureAwait(false);
await ResetIdAsync(id, ClosedToken).ConfigureAwait(false);
//发送成功
await DDPAdapter.SendInputAsync(new DDPSend(ReadOnlyMemory<byte>.Empty, id, true, 0x81), ClosedToken).ConfigureAwait(false);
await base.ProtectedSendAsync(new DDPSend(ReadOnlyMemory<byte>.Empty, id, true, 0x81), ClosedToken).ConfigureAwait(false);
if (log)
Logger?.Info(string.Format(AppResource.DtuConnected, Id));
}
else if (message.Type == 0x02)
{
await DDPAdapter.SendInputAsync(new DDPSend(ReadOnlyMemory<byte>.Empty, Id, true, 0x82), ClosedToken).ConfigureAwait(false);
await base.ProtectedSendAsync(new DDPSend(ReadOnlyMemory<byte>.Empty, Id, true, 0x82), ClosedToken).ConfigureAwait(false);
Logger?.Info(string.Format(AppResource.DtuDisconnecting, Id));
await Task.Delay(100).ConfigureAwait(false);
await this.CloseAsync().ConfigureAwait(false);

View File

@@ -36,16 +36,12 @@ public class DDPUdpSessionChannel : UdpSessionChannel, IClientChannel, IDtuUdpSe
DDPAdapter.Config(Config);
}
// 将当前实例的日志记录器和加载回调设置到适配器中
DDPAdapter.Logger = Logger;
if (DDPAdapter.Owner != null)
{
DDPAdapter.OnLoaded(this);
}
DDPAdapter.SendCallBackAsync = DDPSendAsync;
DDPAdapter.ReceivedCallBack = DDPHandleReceivedData;
DDPAdapter.SendCallBackAsync = base.ProtectedDefaultSendAsync;
DataHandlingAdapter.SendCallBackAsync = DefaultSendAsync;
}
@@ -62,22 +58,7 @@ public class DDPUdpSessionChannel : UdpSessionChannel, IClientChannel, IDtuUdpSe
}
}
protected Task DDPSendAsync(EndPoint endPoint, ReadOnlyMemory<byte> memory, CancellationToken token)
{
//获取endpoint
return base.ProtectedDefaultSendAsync(endPoint, memory, token);
}
private ConcurrentDictionary<EndPoint, DDPMessage> DDPMessageDict { get; set; } = new();
private Task DDPHandleReceivedData(EndPoint endPoint, IByteBlockReader byteBlock, IRequestInfo requestInfo)
{
if (requestInfo is DDPMessage dDPMessage)
{
DDPMessageDict.AddOrUpdate(endPoint, dDPMessage);
}
return EasyTask.CompletedTask;
}
private DeviceUdpDataHandleAdapter<DDPUdpMessage> DDPAdapter = new();
@@ -98,27 +79,14 @@ public class DDPUdpSessionChannel : UdpSessionChannel, IClientChannel, IDtuUdpSe
return base.StopAsync(token);
}
private ConcurrentDictionary<EndPoint, WaitLock> _waitLocks = new();
protected override async ValueTask<bool> OnUdpReceiving(UdpReceiveingEventArgs e)
{
var byteBlock = e.ByteBlock;
var byteBlock = e.Memory;
var endPoint = e.EndPoint;
DDPMessage? message = null;
var waitLock = _waitLocks.GetOrAdd(endPoint, new WaitLock(nameof(DDPUdpSessionChannel)));
try
{
await waitLock.WaitAsync().ConfigureAwait(false);
await DDPAdapter.ReceivedInput(endPoint, byteBlock).ConfigureAwait(EasyTask.ContinueOnCapturedContext);
if (DDPMessageDict.TryGetValue(endPoint, out var dDPMessage))
message = dDPMessage;
DDPMessageDict.TryRemove(endPoint, out _);
}
finally
{
waitLock.Release();
}
if (!DDPAdapter.TryParseRequest(endPoint, byteBlock, out var message))
return true;
if (message != null)
{
@@ -127,15 +95,13 @@ public class DDPUdpSessionChannel : UdpSessionChannel, IClientChannel, IDtuUdpSe
var id = $"ID={message.Id}";
if (message.Type == 0x09)
{
var reader = new ByteBlockReader(message.Content);
if (this.DataHandlingAdapter == null)
{
await this.OnUdpReceived(new UdpReceivedDataEventArgs(endPoint, reader, default)).ConfigureAwait(EasyTask.ContinueOnCapturedContext);
await this.OnUdpReceived(new UdpReceivedDataEventArgs(endPoint, message.Content, default)).ConfigureAwait(false);
}
else
{
await this.DataHandlingAdapter.ReceivedInput(endPoint, reader).ConfigureAwait(EasyTask.ContinueOnCapturedContext);
await this.DataHandlingAdapter.ReceivedInputAsync(endPoint, message.Content).ConfigureAwait(false);
}
return true;

View File

@@ -8,8 +8,6 @@
// QQ群605534569
//------------------------------------------------------------------------------
using System.Collections.Concurrent;
namespace ThingsGateway.Foundation;
/// <summary>
@@ -59,15 +57,9 @@ public interface IChannel : ISetupConfigObject, IDisposable, IClosableClient, IC
/// </summary>
public ChannelEventHandler Stoping { get; }
/// <summary>
/// 主动请求时的等待池
/// </summary>
public ConcurrentDictionary<long, Func<IClientChannel, ReceivedDataEventArgs, bool, Task>> ChannelReceivedWaitDict { get; }
void ResetSign(int minSign = 0, int maxSign = ushort.MaxValue);
}
/// <summary>

View File

@@ -22,6 +22,8 @@ public interface IClientChannel : IChannel, ISender, IClient, IClientSender, IOn
/// </summary>
DataHandlingAdapter ReadOnlyDataHandlingAdapter { get; }
/// <summary>
/// 通道等待池
/// </summary>
@@ -34,4 +36,6 @@ public interface IClientChannel : IChannel, ISender, IClient, IClientSender, IOn
/// </summary>
/// <param name="adapter">适配器</param>
void SetDataHandlingAdapter(DataHandlingAdapter adapter);
void SetDataHandlingAdapterLogger(ILog log);
}

View File

@@ -8,8 +8,6 @@
// QQ群605534569
//------------------------------------------------------------------------------
using System.Collections.Concurrent;
using ThingsGateway.NewLife;
using TouchSocket.SerialPorts;
@@ -31,13 +29,23 @@ public class OtherChannel : SetupConfigObject, IClientChannel
}
public override TouchSocketConfig Config => base.Config ?? ChannelOptions.Config;
public void SetDataHandlingAdapterLogger(ILog log)
{
if (_deviceDataHandleAdapter == null && ReadOnlyDataHandlingAdapter is IDeviceDataHandleAdapter handleAdapter)
{
_deviceDataHandleAdapter = handleAdapter;
}
if (_deviceDataHandleAdapter != null)
{
_deviceDataHandleAdapter.Logger = log;
}
}
public void ResetSign(int minSign = 0, int maxSign = ushort.MaxValue)
{
var pool = WaitHandlePool;
WaitHandlePool = new WaitHandlePool<MessageBase>(minSign, maxSign);
pool?.CancelAll();
pool?.SafeDispose();
}
/// <inheritdoc/>
public ChannelReceivedEventHandler ChannelReceived { get; } = new();
@@ -65,22 +73,25 @@ public class OtherChannel : SetupConfigObject, IClientChannel
/// <summary>
/// 等待池
/// </summary>
public WaitHandlePool<MessageBase> WaitHandlePool { get; internal set; } = new();
public WaitHandlePool<MessageBase> WaitHandlePool { get; internal set; } = new(0, ushort.MaxValue);
/// <inheritdoc/>
public WaitLock WaitLock => ChannelOptions.WaitLock;
public virtual WaitLock GetLock(string key) => WaitLock;
/// <inheritdoc/>
public ConcurrentDictionary<long, Func<IClientChannel, ReceivedDataEventArgs, bool, Task>> ChannelReceivedWaitDict { get; } = new();
//private readonly WaitLock _connectLock = new WaitLock();
private IDeviceDataHandleAdapter _deviceDataHandleAdapter;
/// <inheritdoc/>
public void SetDataHandlingAdapter(DataHandlingAdapter adapter)
{
if (adapter is SingleStreamDataHandlingAdapter singleStreamDataHandlingAdapter)
SetAdapter(singleStreamDataHandlingAdapter);
if (adapter is IDeviceDataHandleAdapter deviceDataHandleAdapter)
_deviceDataHandleAdapter = deviceDataHandleAdapter;
}
/// <summary>
/// 设置数据处理适配器。
@@ -104,20 +115,17 @@ public class OtherChannel : SetupConfigObject, IClientChannel
}
// 设置适配器的日志记录器和加载、接收数据的回调方法。
adapter.Logger = Logger;
adapter.OnLoaded(this);
adapter.ReceivedAsyncCallBack = PrivateHandleReceivedData;
//adapter.SendCallBack = this.ProtectedDefaultSend;
adapter.SendAsyncCallBack = ProtectedDefaultSendAsync;
// 将提供的适配器实例设置为当前实例的数据处理适配器。
m_dataHandlingAdapter = adapter;
}
private async Task PrivateHandleReceivedData(IByteBlockReader byteBlock, IRequestInfo requestInfo)
private Task PrivateHandleReceivedData(ReadOnlyMemory<byte> byteBlock, IRequestInfo requestInfo)
{
LastReceivedTime = DateTime.Now;
await this.OnChannelReceivedEvent(new ReceivedDataEventArgs(byteBlock, requestInfo), ChannelReceived).ConfigureAwait(false);
return this.OnChannelReceivedEvent(new ReceivedDataEventArgs(byteBlock, requestInfo), ChannelReceived);
}
/// <summary>
@@ -154,7 +162,8 @@ public class OtherChannel : SetupConfigObject, IClientChannel
return Task.FromResult(Result.Success);
}
public volatile bool online;
public Task ConnectAsync(int millisecondsTimeout, CancellationToken token)
public Task ConnectAsync(CancellationToken token)
{
var cts = m_transport;
m_transport = new();
@@ -180,8 +189,11 @@ public class OtherChannel : SetupConfigObject, IClientChannel
}
else
{
// 否则,使用适配器的发送方法进行数据发送。
return m_dataHandlingAdapter.SendInputAsync(memory, cancellationToken);
var byteBlock = new ByteBlock(1024);
m_dataHandlingAdapter.SendInput(ref byteBlock, memory);
byteBlock.SafeDispose();
return EasyTask.CompletedTask;
}
}
@@ -190,9 +202,14 @@ public class OtherChannel : SetupConfigObject, IClientChannel
// 检查是否具备发送请求的条件,如果不具备则抛出异常
ThrowIfCannotSendRequestInfo();
// 使用数据处理适配器异步发送输入请求
return m_dataHandlingAdapter.SendInputAsync(requestInfo, cancellationToken);
var byteBlock = new ByteBlock(1024);
m_dataHandlingAdapter.SendInput(ref byteBlock, requestInfo);
byteBlock.SafeDispose();
return EasyTask.CompletedTask;
}
private void ThrowIfCannotSendRequestInfo()
{
if (m_dataHandlingAdapter?.CanSendRequestInfo != true)
@@ -200,4 +217,6 @@ public class OtherChannel : SetupConfigObject, IClientChannel
throw new NotSupportedException($"当前适配器为空或者不支持对象发送。");
}
}
}

View File

@@ -59,19 +59,18 @@ public class DtuPlugin : PluginBase, ITcpReceivingPlugin
public bool DtuIdHex { get; set; }
/// <inheritdoc/>
public async Task OnTcpReceiving(ITcpSession client, ByteBlockEventArgs e)
public async Task OnTcpReceiving(ITcpSession client, BytesReaderEventArgs e)
{
var len = HeartbeatByte.Length;
if (client is TcpSessionClientChannel socket && socket.Service is ITcpServiceChannel tcpServiceChannel)
{
if (!socket.Id.StartsWith("ID="))
{
var id = DtuIdHex ? $"ID={e.ByteBlock.Span.ToHexString()}" : $"ID={e.ByteBlock.ToString(0, e.ByteBlock.Length)}";
var id = DtuIdHex ? $"ID={e.Reader.ToHexString()}" : $"ID={e.Reader.ToString()}";
if (tcpServiceChannel.TryGetClient(id, out var oldClient))
{
try
{
//await oldClient.ShutdownAsync(System.Net.Sockets.SocketShutdown.Both).ConfigureAwait(false);
await oldClient.CloseAsync().ConfigureAwait(false);
oldClient.Dispose();
}
@@ -79,7 +78,7 @@ public class DtuPlugin : PluginBase, ITcpReceivingPlugin
{
}
}
await socket.ResetIdAsync(id).ConfigureAwait(false);
await socket.ResetIdAsync(id, client.ClosedToken).ConfigureAwait(false);
client.Logger?.Info(string.Format(AppResource.DtuConnected, id));
e.Handled = true;
}
@@ -88,7 +87,6 @@ public class DtuPlugin : PluginBase, ITcpReceivingPlugin
{
try
{
//await socket.ShutdownAsync(System.Net.Sockets.SocketShutdown.Both).ConfigureAwait(false);
await socket.CloseAsync().ConfigureAwait(false);
socket.Dispose();
}
@@ -102,11 +100,11 @@ public class DtuPlugin : PluginBase, ITcpReceivingPlugin
if (len > 0)
{
if (HeartbeatByte.Span.SequenceEqual(e.ByteBlock.Memory.Slice(0, len).Span))
if (HeartbeatByte.Span.SequenceEqual(e.Reader.TotalSequence.Slice(0, len).First.Span))
{
if (DateTimeOffset.Now - socket.LastSentTime < TimeSpan.FromMilliseconds(200))
{
await Task.Delay(200).ConfigureAwait(false);
await Task.Delay(200, client.ClosedToken).ConfigureAwait(false);
}
//回应心跳包
await socket.SendAsync(HeartbeatByte, socket.ClosedToken).ConfigureAwait(false);
@@ -118,4 +116,6 @@ public class DtuPlugin : PluginBase, ITcpReceivingPlugin
}
await e.InvokeNext().ConfigureAwait(false);//如果本插件无法处理当前数据,请将数据转至下一个插件。
}
}

View File

@@ -105,7 +105,7 @@ internal sealed class HeartbeatAndReceivePlugin : PluginBase, ITcpConnectedPlugi
}
}
}
private Task Task;
private Task _task;
private bool SendHeartbeat;
public int HeartbeatTime { get; set; } = 3000;
@@ -125,17 +125,17 @@ internal sealed class HeartbeatAndReceivePlugin : PluginBase, ITcpConnectedPlugi
{
await tcpClient.SendAsync(DtuIdByte, tcpClient.ClosedToken).ConfigureAwait(false);
if (Task == null)
if (_task == null)
{
Task = Task.Factory.StartNew(async () =>
_task = Task.Run(async () =>
{
var failedCount = 0;
while (SendHeartbeat)
{
await Task.Delay(HeartbeatTime).ConfigureAwait(false);
await Task.Delay(HeartbeatTime, client.ClosedToken).ConfigureAwait(false);
if (!client.Online)
{
continue;
break;
}
try
@@ -159,15 +159,15 @@ internal sealed class HeartbeatAndReceivePlugin : PluginBase, ITcpConnectedPlugi
}
}
Task = null;
}, CancellationToken.None, TaskCreationOptions.None, TaskScheduler.Default);
_task = null;
});
}
}
await e.InvokeNext().ConfigureAwait(false);
}
public async Task OnTcpReceiving(ITcpSession client, ByteBlockEventArgs e)
public async Task OnTcpReceiving(ITcpSession client, BytesReaderEventArgs e)
{
if (client is ITcpSessionClient)
{
@@ -181,7 +181,7 @@ internal sealed class HeartbeatAndReceivePlugin : PluginBase, ITcpConnectedPlugi
var len = HeartbeatByte.Length;
if (len > 0)
{
if (HeartbeatByte.Span.SequenceEqual(e.ByteBlock.Memory.Slice(0, len).Span))
if (HeartbeatByte.Span.SequenceEqual(e.Reader.TotalSequence.Slice(0, len).First.Span))
{
e.Handled = true;
}
@@ -190,6 +190,7 @@ internal sealed class HeartbeatAndReceivePlugin : PluginBase, ITcpConnectedPlugi
}
}
public Task OnTcpClosed(ITcpSession client, ClosedEventArgs e)
{
SendHeartbeat = false;

View File

@@ -77,10 +77,9 @@ public static class PluginUtil
a.UseTcpSessionCheckClear()
.SetCheckClearType(CheckClearType.All)
.SetTick(TimeSpan.FromMilliseconds(channelOptions.CheckClearTime))
.SetOnClose(async (c, t) =>
.SetOnClose((c, t) =>
{
//await c.ShutdownAsync(System.Net.Sockets.SocketShutdown.Both).ConfigureAwait(false);
await c.CloseAsync($"{channelOptions.CheckClearTime}ms Timeout").ConfigureAwait(false);
return c.CloseAsync($"{channelOptions.CheckClearTime}ms Timeout");
});
};
}

View File

@@ -8,8 +8,6 @@
// QQ群605534569
//------------------------------------------------------------------------------
using System.Collections.Concurrent;
using ThingsGateway.NewLife;
using TouchSocket.SerialPorts;
@@ -34,7 +32,6 @@ public class SerialPortChannel : SerialPortClient, IClientChannel
var pool = WaitHandlePool;
WaitHandlePool = new WaitHandlePool<MessageBase>(minSign, maxSign);
pool?.CancelAll();
pool?.SafeDispose();
}
/// <inheritdoc/>
public ChannelReceivedEventHandler ChannelReceived { get; } = new();
@@ -50,6 +47,26 @@ public class SerialPortChannel : SerialPortClient, IClientChannel
/// <inheritdoc/>
public DataHandlingAdapter ReadOnlyDataHandlingAdapter => ProtectedDataHandlingAdapter;
private IDeviceDataHandleAdapter _deviceDataHandleAdapter;
public void SetDataHandlingAdapterLogger(ILog log)
{
if (_deviceDataHandleAdapter == null && ProtectedDataHandlingAdapter is IDeviceDataHandleAdapter handleAdapter)
{
_deviceDataHandleAdapter = handleAdapter;
}
if (_deviceDataHandleAdapter != null)
{
_deviceDataHandleAdapter.Logger = log;
}
}
/// <inheritdoc/>
public void SetDataHandlingAdapter(DataHandlingAdapter adapter)
{
if (adapter is SingleStreamDataHandlingAdapter singleStreamDataHandlingAdapter)
SetAdapter(singleStreamDataHandlingAdapter);
if (adapter is IDeviceDataHandleAdapter deviceDataHandleAdapter)
_deviceDataHandleAdapter = deviceDataHandleAdapter;
}
/// <inheritdoc/>
public ChannelEventHandler Started { get; } = new();
@@ -65,14 +82,13 @@ public class SerialPortChannel : SerialPortClient, IClientChannel
/// <summary>
/// 等待池
/// </summary>
public WaitHandlePool<MessageBase> WaitHandlePool { get; internal set; } = new();
public WaitHandlePool<MessageBase> WaitHandlePool { get; internal set; } = new(0, ushort.MaxValue);
/// <inheritdoc/>
public WaitLock WaitLock => ChannelOptions.WaitLock;
public virtual WaitLock GetLock(string key) => WaitLock;
/// <inheritdoc/>
public ConcurrentDictionary<long, Func<IClientChannel, ReceivedDataEventArgs, bool, Task>> ChannelReceivedWaitDict { get; } = new();
//private readonly WaitLock _connectLock = new WaitLock();
/// <inheritdoc/>
@@ -86,6 +102,7 @@ public class SerialPortChannel : SerialPortClient, IClientChannel
if (Online)
{
PortName = null;
await this.OnChannelEvent(Stoping).ConfigureAwait(false);
var result = await base.CloseAsync(msg, token).ConfigureAwait(false);
if (!Online)
{
@@ -103,7 +120,7 @@ public class SerialPortChannel : SerialPortClient, IClientChannel
}
/// <inheritdoc/>
public override async Task ConnectAsync(int millisecondsTimeout, CancellationToken token)
public override async Task ConnectAsync(CancellationToken token)
{
if (!Online)
{
@@ -119,7 +136,8 @@ public class SerialPortChannel : SerialPortClient, IClientChannel
if (port != null)
PortName = $"{port.PortName}";
await base.ConnectAsync(millisecondsTimeout, token).ConfigureAwait(false);
await this.OnChannelEvent(Starting).ConfigureAwait(false);
await base.ConnectAsync(token).ConfigureAwait(false);
if (Online)
{
if (token.IsCancellationRequested) return;
@@ -134,12 +152,7 @@ public class SerialPortChannel : SerialPortClient, IClientChannel
}
}
/// <inheritdoc/>
public void SetDataHandlingAdapter(DataHandlingAdapter adapter)
{
if (adapter is SingleStreamDataHandlingAdapter singleStreamDataHandlingAdapter)
SetAdapter(singleStreamDataHandlingAdapter);
}
private string PortName { get; set; }
/// <inheritdoc/>
public override string? ToString()
@@ -153,54 +166,43 @@ public class SerialPortChannel : SerialPortClient, IClientChannel
return base.ToString();
}
protected override async Task OnSerialClosed(ClosedEventArgs e)
protected override Task OnSerialClosed(ClosedEventArgs e)
{
Logger?.Info($"{ToString()} Closed{(e.Message.IsNullOrEmpty() ? string.Empty : $" -{e.Message}")}");
await base.OnSerialClosed(e).ConfigureAwait(false);
return base.OnSerialClosed(e);
}
/// <inheritdoc/>
protected override async Task OnSerialClosing(ClosingEventArgs e)
protected override Task OnSerialClosing(ClosingEventArgs e)
{
await this.OnChannelEvent(Stoping).ConfigureAwait(false);
Logger?.Trace($"{ToString()} Closing{(e.Message.IsNullOrEmpty() ? string.Empty : $" -{e.Message}")}");
await base.OnSerialClosing(e).ConfigureAwait(false);
return base.OnSerialClosing(e);
}
/// <inheritdoc/>
protected override async Task OnSerialConnecting(ConnectingEventArgs e)
protected override Task OnSerialConnecting(ConnectingEventArgs e)
{
Logger?.Trace($"{ToString()} Connecting{(e.Message.IsNullOrEmpty() ? string.Empty : $" -{e.Message}")}");
await this.OnChannelEvent(Starting).ConfigureAwait(false);
await base.OnSerialConnecting(e).ConfigureAwait(false);
return base.OnSerialConnecting(e);
}
protected override async Task OnSerialConnected(ConnectedEventArgs e)
protected override Task OnSerialConnected(ConnectedEventArgs e)
{
Logger?.Debug($"{ToString()} Connected");
await base.OnSerialConnected(e).ConfigureAwait(false);
return base.OnSerialConnected(e);
}
/// <inheritdoc/>
protected override async Task OnSerialReceived(ReceivedDataEventArgs e)
{
await base.OnSerialReceived(e).ConfigureAwait(false);
if (e.RequestInfo is MessageBase response)
{
if (ChannelReceivedWaitDict.TryRemove(response.Sign, out var func))
{
await func.Invoke(this, e, ChannelReceived.Count == 1).ConfigureAwait(false);
e.Handled = true;
}
}
if (e.Handled)
return;
await this.OnChannelReceivedEvent(e, ChannelReceived).ConfigureAwait(false);
}
/// <inheritdoc/>
protected override void SafetyDispose(bool disposing)
{
WaitHandlePool.SafeDispose();
WaitHandlePool?.CancelAll();
base.SafetyDispose(disposing);
}
}

View File

@@ -8,8 +8,6 @@
// QQ群605534569
//------------------------------------------------------------------------------
using System.Collections.Concurrent;
using ThingsGateway.NewLife;
namespace ThingsGateway.Foundation;
@@ -31,9 +29,27 @@ public class TcpClientChannel : TcpClient, IClientChannel
var pool = WaitHandlePool;
WaitHandlePool = new WaitHandlePool<MessageBase>(minSign, maxSign);
pool?.CancelAll();
pool?.SafeDispose();
}
private IDeviceDataHandleAdapter _deviceDataHandleAdapter;
public void SetDataHandlingAdapterLogger(ILog log)
{
if (_deviceDataHandleAdapter == null && DataHandlingAdapter is IDeviceDataHandleAdapter handleAdapter)
{
_deviceDataHandleAdapter = handleAdapter;
}
if (_deviceDataHandleAdapter != null)
{
_deviceDataHandleAdapter.Logger = log;
}
}
/// <inheritdoc/>
public void SetDataHandlingAdapter(DataHandlingAdapter adapter)
{
if (adapter is SingleStreamDataHandlingAdapter singleStreamDataHandlingAdapter)
SetAdapter(singleStreamDataHandlingAdapter);
if (adapter is IDeviceDataHandleAdapter deviceDataHandleAdapter)
_deviceDataHandleAdapter = deviceDataHandleAdapter;
}
/// <inheritdoc/>
public ChannelReceivedEventHandler ChannelReceived { get; } = new();
@@ -62,14 +78,13 @@ public class TcpClientChannel : TcpClient, IClientChannel
/// <summary>
/// 等待池
/// </summary>
public WaitHandlePool<MessageBase> WaitHandlePool { get; internal set; } = new();
public WaitHandlePool<MessageBase> WaitHandlePool { get; internal set; } = new(0, ushort.MaxValue);
public virtual WaitLock GetLock(string key) => WaitLock;
/// <inheritdoc/>
public WaitLock WaitLock => ChannelOptions.WaitLock;
/// <inheritdoc/>
public ConcurrentDictionary<long, Func<IClientChannel, ReceivedDataEventArgs, bool, Task>> ChannelReceivedWaitDict { get; } = new();
//private readonly WaitLock _connectLock = new WaitLock();
/// <inheritdoc/>
@@ -82,6 +97,7 @@ public class TcpClientChannel : TcpClient, IClientChannel
//await _connectLock.WaitAsync().ConfigureAwait(false);
if (Online)
{
await this.OnChannelEvent(Stoping).ConfigureAwait(false);
var result = await base.CloseAsync(msg, token).ConfigureAwait(false);
if (!Online)
{
@@ -99,7 +115,7 @@ public class TcpClientChannel : TcpClient, IClientChannel
}
/// <inheritdoc/>
public override async Task ConnectAsync(int millisecondsTimeout, CancellationToken token)
public override async Task ConnectAsync(CancellationToken token)
{
if (!Online)
{
@@ -109,7 +125,8 @@ public class TcpClientChannel : TcpClient, IClientChannel
if (!Online)
{
if (token.IsCancellationRequested) return;
await base.ConnectAsync(millisecondsTimeout, token).ConfigureAwait(false);
await this.OnChannelEvent(Starting).ConfigureAwait(false);
await base.ConnectAsync(token).ConfigureAwait(false);
if (Online)
{
if (token.IsCancellationRequested) return;
@@ -124,12 +141,6 @@ public class TcpClientChannel : TcpClient, IClientChannel
}
}
/// <inheritdoc/>
public void SetDataHandlingAdapter(DataHandlingAdapter adapter)
{
if (adapter is SingleStreamDataHandlingAdapter singleStreamDataHandlingAdapter)
SetAdapter(singleStreamDataHandlingAdapter);
}
/// <inheritdoc/>
public override string ToString()
@@ -137,48 +148,39 @@ public class TcpClientChannel : TcpClient, IClientChannel
return $"{IP}:{Port}";
}
protected override async Task OnTcpClosed(ClosedEventArgs e)
protected override Task OnTcpClosed(ClosedEventArgs e)
{
Logger?.Info($"{ToString()} Closed{(e.Message.IsNullOrEmpty() ? string.Empty : $" -{e.Message}")}");
await base.OnTcpClosed(e).ConfigureAwait(false);
return base.OnTcpClosed(e);
}
/// <inheritdoc/>
protected override async Task OnTcpClosing(ClosingEventArgs e)
protected override Task OnTcpClosing(ClosingEventArgs e)
{
await this.OnChannelEvent(Stoping).ConfigureAwait(false);
Logger?.Trace($"{ToString()} Closing{(e.Message.IsNullOrEmpty() ? string.Empty : $" -{e.Message}")}");
await base.OnTcpClosing(e).ConfigureAwait(false);
return base.OnTcpClosing(e);
}
/// <inheritdoc/>
protected override async Task OnTcpConnecting(ConnectingEventArgs e)
protected override Task OnTcpConnecting(ConnectingEventArgs e)
{
Logger?.Trace($"{ToString()} Connecting{(e.Message.IsNullOrEmpty() ? string.Empty : $"-{e.Message}")}");
await this.OnChannelEvent(Starting).ConfigureAwait(false);
await base.OnTcpConnecting(e).ConfigureAwait(false);
return base.OnTcpConnecting(e);
}
protected override async Task OnTcpConnected(ConnectedEventArgs e)
protected override Task OnTcpConnected(ConnectedEventArgs e)
{
Logger?.Info($"{ToString()} Connected");
await base.OnTcpConnected(e).ConfigureAwait(false);
return base.OnTcpConnected(e);
}
/// <inheritdoc/>
protected override async Task OnTcpReceived(ReceivedDataEventArgs e)
{
await base.OnTcpReceived(e).ConfigureAwait(false);
if (e.RequestInfo is MessageBase response)
{
if (ChannelReceivedWaitDict.TryRemove(response.Sign, out var func))
{
await func.Invoke(this, e, ChannelReceived.Count == 1).ConfigureAwait(false);
e.Handled = true;
}
}
if (e.Handled)
return;
@@ -188,7 +190,7 @@ public class TcpClientChannel : TcpClient, IClientChannel
/// <inheritdoc/>
protected override void SafetyDispose(bool disposing)
{
WaitHandlePool.SafeDispose();
WaitHandlePool?.CancelAll();
base.SafetyDispose(disposing);
}
}

View File

@@ -8,8 +8,6 @@
// QQ群605534569
//------------------------------------------------------------------------------
using System.Collections.Concurrent;
using ThingsGateway.NewLife;
namespace ThingsGateway.Foundation;
@@ -23,30 +21,6 @@ public abstract class TcpServiceChannelBase<TClient> : TcpService<TClient>, ITcp
/// <inheritdoc/>
public ConcurrentList<IDevice> 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)
// await client.ShutdownAsync(System.Net.Sockets.SocketShutdown.Both).ConfigureAwait(false);
await client.CloseAsync().ConfigureAwait(false);
client.SafeDispose();
}
catch
{
}
}
}
public async Task ClientDisposeAsync(string id)
{
if (this.TryGetClient(id, out var client))
@@ -136,6 +110,7 @@ public abstract class TcpServiceChannelBase<TClient> : TcpService<TClient>, ITcp
{
m_transport?.SafeCancel();
m_transport?.SafeDispose();
m_transport = null;
base.SafetyDispose(disposing);
}
/// <inheritdoc/>
@@ -209,7 +184,7 @@ public class TcpServiceChannel<TClient> : TcpServiceChannelBase<TClient>, IChann
}
/// <inheritdoc/>
public Task ConnectAsync(int timeout = 3000, CancellationToken token = default)
public Task ConnectAsync(CancellationToken token = default)
{
if (token.IsCancellationRequested)
return EasyTask.CompletedTask;
@@ -268,22 +243,13 @@ public class TcpServiceChannel<TClient> : TcpServiceChannelBase<TClient>, IChann
{
await base.OnTcpReceived(socketClient, e).ConfigureAwait(false);
if (e.RequestInfo is MessageBase response)
{
if (ChannelReceivedWaitDict.TryRemove(response.Sign, out var func))
{
await func.Invoke(socketClient, e, ChannelReceived.Count == 1).ConfigureAwait(false);
e.Handled = true;
}
}
if (e.Handled)
return;
await socketClient.OnChannelReceivedEvent(e, ChannelReceived).ConfigureAwait(false);
}
/// <inheritdoc/>
public ConcurrentDictionary<long, Func<IClientChannel, ReceivedDataEventArgs, bool, Task>> ChannelReceivedWaitDict { get; } = new();
IEnumerable<TcpSessionClientChannel> ITcpServiceChannel.Clients => base.Clients;

View File

@@ -8,8 +8,6 @@
// QQ群605534569
//------------------------------------------------------------------------------
using System.Collections.Concurrent;
using ThingsGateway.NewLife;
namespace ThingsGateway.Foundation;
@@ -23,13 +21,31 @@ public class TcpSessionClientChannel : TcpSessionClient, IClientChannel
public TcpSessionClientChannel()
{
}
private IDeviceDataHandleAdapter _deviceDataHandleAdapter;
public void SetDataHandlingAdapterLogger(ILog log)
{
if (_deviceDataHandleAdapter == null && DataHandlingAdapter is IDeviceDataHandleAdapter handleAdapter)
{
_deviceDataHandleAdapter = handleAdapter;
}
if (_deviceDataHandleAdapter != null)
{
_deviceDataHandleAdapter.Logger = log;
}
}
/// <inheritdoc/>
public void SetDataHandlingAdapter(DataHandlingAdapter adapter)
{
if (adapter is SingleStreamDataHandlingAdapter singleStreamDataHandlingAdapter)
SetAdapter(singleStreamDataHandlingAdapter);
if (adapter is IDeviceDataHandleAdapter deviceDataHandleAdapter)
_deviceDataHandleAdapter = deviceDataHandleAdapter;
}
public void ResetSign(int minSign = 0, int maxSign = ushort.MaxValue)
{
var pool = WaitHandlePool;
WaitHandlePool = new WaitHandlePool<MessageBase>(minSign, maxSign);
pool?.CancelAll();
pool?.SafeDispose();
}
/// <inheritdoc/>
public ChannelReceivedEventHandler ChannelReceived { get; } = new();
@@ -60,7 +76,7 @@ public class TcpSessionClientChannel : TcpSessionClient, IClientChannel
/// <summary>
/// 等待池
/// </summary>
public WaitHandlePool<MessageBase> WaitHandlePool { get; private set; } = new();
public WaitHandlePool<MessageBase> WaitHandlePool { get; private set; } = new(0, ushort.MaxValue);
/// <inheritdoc/>
public WaitLock WaitLock { get; internal set; } = new(nameof(TcpSessionClientChannel));
@@ -69,25 +85,19 @@ public class TcpSessionClientChannel : TcpSessionClient, IClientChannel
/// <inheritdoc/>
public override Task<Result> CloseAsync(string msg, CancellationToken token)
{
WaitHandlePool.SafeDispose();
WaitHandlePool?.CancelAll();
return base.CloseAsync(msg, token);
}
/// <inheritdoc/>
public Task ConnectAsync(int timeout, CancellationToken token) => Task.CompletedTask;
public Task ConnectAsync(CancellationToken token) => Task.CompletedTask;
/// <inheritdoc/>
public void SetDataHandlingAdapter(DataHandlingAdapter adapter)
{
if (adapter is SingleStreamDataHandlingAdapter singleStreamDataHandlingAdapter)
SetAdapter(singleStreamDataHandlingAdapter);
}
/// <inheritdoc/>
public Task SetupAsync(TouchSocketConfig config) => Task.CompletedTask;
/// <inheritdoc/>
public ConcurrentDictionary<long, Func<IClientChannel, ReceivedDataEventArgs, bool, Task>> ChannelReceivedWaitDict { get; } = new();
/// <inheritdoc/>
public override string ToString()
@@ -98,7 +108,7 @@ public class TcpSessionClientChannel : TcpSessionClient, IClientChannel
/// <inheritdoc/>
protected override void SafetyDispose(bool disposing)
{
WaitHandlePool.SafeDispose();
WaitHandlePool?.CancelAll();
base.SafetyDispose(disposing);
}
@@ -136,14 +146,7 @@ public class TcpSessionClientChannel : TcpSessionClient, IClientChannel
protected override async Task OnTcpReceived(ReceivedDataEventArgs e)
{
await base.OnTcpReceived(e).ConfigureAwait(false);
if (e.RequestInfo is MessageBase response)
{
if (ChannelReceivedWaitDict.TryRemove(response.Sign, out var func))
{
await func.Invoke(this, e, ChannelReceived.Count == 1).ConfigureAwait(false);
e.Handled = true;
}
}
if (e.Handled)
return;

View File

@@ -8,8 +8,6 @@
// QQ群605534569
//------------------------------------------------------------------------------
using System.Collections.Concurrent;
using ThingsGateway.NewLife;
namespace ThingsGateway.Foundation;
@@ -28,13 +26,31 @@ public class UdpSessionChannel : UdpSession, IClientChannel
ResetSign();
}
public override TouchSocketConfig Config => base.Config ?? ChannelOptions.Config;
private IDeviceDataHandleAdapter _deviceDataHandleAdapter;
public void SetDataHandlingAdapterLogger(ILog log)
{
if (_deviceDataHandleAdapter == null && DataHandlingAdapter is IDeviceDataHandleAdapter handleAdapter)
{
_deviceDataHandleAdapter = handleAdapter;
}
if (_deviceDataHandleAdapter != null)
{
_deviceDataHandleAdapter.Logger = log;
}
}
/// <inheritdoc/>
public void SetDataHandlingAdapter(DataHandlingAdapter adapter)
{
if (adapter is UdpDataHandlingAdapter udpDataHandlingAdapter)
SetAdapter(udpDataHandlingAdapter);
if (adapter is IDeviceDataHandleAdapter deviceDataHandleAdapter)
_deviceDataHandleAdapter = deviceDataHandleAdapter;
}
public void ResetSign(int minSign = 0, int maxSign = ushort.MaxValue)
{
var pool = WaitHandlePool;
WaitHandlePool = new WaitHandlePool<MessageBase>(minSign, maxSign);
pool?.CancelAll();
pool?.SafeDispose();
}
/// <inheritdoc/>
@@ -69,14 +85,13 @@ public class UdpSessionChannel : UdpSession, IClientChannel
/// <summary>
/// 等待池
/// </summary>
public WaitHandlePool<MessageBase> WaitHandlePool { get; set; } = new();
public WaitHandlePool<MessageBase> WaitHandlePool { get; set; } = new(0, ushort.MaxValue);
/// <inheritdoc/>
public WaitLock WaitLock => ChannelOptions.WaitLock;
public virtual WaitLock GetLock(string key) => WaitLock;
/// <inheritdoc/>
public ConcurrentDictionary<long, Func<IClientChannel, ReceivedDataEventArgs, bool, Task>> ChannelReceivedWaitDict { get; } = new();
/// <inheritdoc/>
public Task<Result> CloseAsync(string msg, CancellationToken token)
@@ -85,19 +100,14 @@ public class UdpSessionChannel : UdpSession, IClientChannel
}
/// <inheritdoc/>
public async Task ConnectAsync(int timeout = 3000, CancellationToken token = default)
public Task ConnectAsync(CancellationToken token = default)
{
if (token.IsCancellationRequested)
return;
await StartAsync().ConfigureAwait(false);
return EasyTask.CompletedTask; ;
return StartAsync();
}
/// <inheritdoc/>
public void SetDataHandlingAdapter(DataHandlingAdapter adapter)
{
if (adapter is UdpDataHandlingAdapter udpDataHandlingAdapter)
SetAdapter(udpDataHandlingAdapter);
}
public CancellationToken ClosedToken => this.m_transport == null ? new CancellationToken(true) : this.m_transport.Token;
private CancellationTokenSource m_transport;
/// <inheritdoc/>
@@ -188,14 +198,6 @@ public class UdpSessionChannel : UdpSession, IClientChannel
{
await base.OnUdpReceived(e).ConfigureAwait(false);
if (e.RequestInfo is MessageBase response)
{
if (ChannelReceivedWaitDict.TryRemove(response.Sign, out var func))
{
await func.Invoke(this, e, ChannelReceived.Count == 1).ConfigureAwait(false);
e.Handled = true;
}
}
if (e.Handled)
return;
@@ -207,7 +209,8 @@ public class UdpSessionChannel : UdpSession, IClientChannel
{
m_transport?.SafeCancel();
m_transport?.SafeDispose();
WaitHandlePool.SafeDispose();
m_transport = null;
WaitHandlePool?.CancelAll();
base.SafetyDispose(disposing);
}
}

View File

@@ -15,15 +15,18 @@ namespace ThingsGateway.Foundation;
/// <summary>
/// TCP/Serial适配器基类
/// </summary>
public class DeviceSingleStreamDataHandleAdapter<TRequest> : TcpCustomDataHandlingAdapter<TRequest> where TRequest : MessageBase, new()
public class DeviceSingleStreamDataHandleAdapter<TRequest> : CustomDataHandlingAdapter<TRequest>, IDeviceDataHandleAdapter where TRequest : MessageBase, new()
{
public new ILog Logger { get; set; }
/// <inheritdoc cref="DeviceSingleStreamDataHandleAdapter{TRequest}"/>
public DeviceSingleStreamDataHandleAdapter()
{
CacheTimeoutEnable = true;
SurLength = int.MaxValue;
}
/// <inheritdoc/>
public override bool CanSendRequestInfo => true;
@@ -40,11 +43,11 @@ public class DeviceSingleStreamDataHandleAdapter<TRequest> : TcpCustomDataHandli
public TRequest Request { get; set; }
/// <inheritdoc />
public void SetRequest(ISendMessage sendMessage, ref ValueByteBlock byteBlock)
public void SetRequest(ISendMessage sendMessage)
{
var request = GetInstance();
request.Sign = sendMessage.Sign;
request.SendInfo(sendMessage, ref byteBlock);
request.SendInfo(sendMessage);
Request = request;
}
@@ -55,24 +58,24 @@ public class DeviceSingleStreamDataHandleAdapter<TRequest> : TcpCustomDataHandli
}
/// <inheritdoc />
protected override FilterResult Filter<TByteBlock>(ref TByteBlock byteBlock, bool beCached, ref TRequest request, ref int tempCapacity)
protected override FilterResult Filter<TReader>(ref TReader byteBlock, bool beCached, ref TRequest request)
{
if (Logger?.LogLevel <= LogLevel.Trace)
Logger?.Trace($"{ToString()}- Receive:{(IsHexLog ? byteBlock.AsSegmentTake().ToHexString(' ') : byteBlock.ToString(byteBlock.Position))}");
Logger?.Trace($"{ToString()}- Receive:{(IsHexLog ? byteBlock.ToHexString(byteBlock.BytesRead, ' ') : byteBlock.ToString(byteBlock.BytesRead))}");
try
{
if (IsSingleThread)
request = Request == null ? GetInstance() : Request;
request = Request == null ? Request = GetInstance() : Request;
else
{
if (!beCached)
request = GetInstance();
}
var pos = byteBlock.Position;
var pos = byteBlock.BytesRead;
if (request.HeaderLength > byteBlock.CanReadLength)
if (request.HeaderLength > byteBlock.BytesRemaining)
{
return FilterResult.Cache;//当头部都无法解析时,直接缓存
}
@@ -80,19 +83,18 @@ public class DeviceSingleStreamDataHandleAdapter<TRequest> : TcpCustomDataHandli
//检查头部合法性
if (request.CheckHead(ref byteBlock))
{
byteBlock.Position = pos;
byteBlock.BytesRead = pos;
if (request.BodyLength > MaxPackageSize)
{
request.OperCode = -1;
request.ErrorMessage = $"Received BodyLength={request.BodyLength}, greater than the set MaxPackageSize={MaxPackageSize}";
OnError(default, request.ErrorMessage, true, true);
SetResult(request);
Reset();
Logger?.LogWarning($"{ToString()} {request.ErrorMessage}");
return FilterResult.GoOn;
}
if (request.BodyLength + request.HeaderLength > byteBlock.CanReadLength)
if (request.BodyLength + request.HeaderLength > byteBlock.BytesRemaining)
{
//body不满足解析开始缓存然后保存对象
tempCapacity = request.BodyLength + request.HeaderLength;
return FilterResult.Cache;
}
//if (request.BodyLength <= 0)
@@ -101,56 +103,47 @@ public class DeviceSingleStreamDataHandleAdapter<TRequest> : TcpCustomDataHandli
// request.BodyLength = byteBlock.Length;
//}
var headPos = pos + request.HeaderLength;
byteBlock.Position = headPos;
byteBlock.BytesRead = headPos;
var result = request.CheckBody(ref byteBlock);
if (result == FilterResult.Cache)
{
byteBlock.BytesRead = pos;
if (Logger?.LogLevel <= LogLevel.Trace)
Logger?.Trace($"{ToString()}-Received incomplete, cached message, current length:{byteBlock.Length} {request?.ErrorMessage}");
tempCapacity = request.BodyLength + request.HeaderLength;
Logger?.Trace($"{ToString()}-Received incomplete, cached message, need length:{request.HeaderLength + request.BodyLength} ,current length:{byteBlock.BytesRead + byteBlock.BytesRemaining} {request?.ErrorMessage}");
request.OperCode = -1;
}
else if (result == FilterResult.GoOn)
{
var addLen = request.HeaderLength + request.BodyLength;
byteBlock.Position = pos + (addLen > 0 ? addLen : 1);
Logger?.Trace($"{ToString()}-{request?.ToString()}");
byteBlock.BytesRead = pos + (addLen > 0 ? addLen : 1);
if (Logger?.LogLevel <= LogLevel.Trace)
Logger?.Trace($"{ToString()}-{request?.ToString()}");
request.OperCode = -1;
SetResult(request);
}
else if (result == FilterResult.Success)
{
var addLen = request.HeaderLength + request.BodyLength;
byteBlock.Position = pos + (addLen > 0 ? addLen : 1);
byteBlock.BytesRead = pos + (addLen > 0 ? addLen : 1);
}
return result;
}
else
{
byteBlock.Position = pos + 1;//移动游标
byteBlock.BytesRead = pos + 1;//移动游标
request.OperCode = -1;
SetResult(request);
return FilterResult.GoOn;//放弃解析
}
}
catch (Exception ex)
{
Logger?.LogWarning(ex, $"{ToString()} Received parsing error");
byteBlock.Position = byteBlock.Length;//移动游标
byteBlock.BytesRead = byteBlock.BytesRead + byteBlock.BytesRemaining;//移动游标
request.Exception = ex;
request.OperCode = -1;
SetResult(request);
return FilterResult.GoOn;//放弃解析
}
}
private void SetResult(TRequest request)
{
if ((Owner as IClientChannel)?.WaitHandlePool?.TryGetDataAsync(request.Sign, out var waitDataAsync) == true)
{
waitDataAsync.SetResult(request);
}
}
/// <summary>
/// 获取泛型实例。
@@ -161,47 +154,32 @@ public class DeviceSingleStreamDataHandleAdapter<TRequest> : TcpCustomDataHandli
return new TRequest() { OperCode = -1, Sign = -1 };
}
/// <inheritdoc/>
protected override void OnReceivedSuccess(TRequest request)
public override void SendInput<TWriter>(ref TWriter writer, in ReadOnlyMemory<byte> memory)
{
Request = null;
}
/// <inheritdoc />
protected override async Task PreviewSendAsync(ReadOnlyMemory<byte> memory, CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
if (Logger?.LogLevel <= LogLevel.Trace)
Logger?.Trace($"{ToString()}- Send:{(IsHexLog ? memory.Span.ToHexString(' ') : (memory.Span.ToString(Encoding.UTF8)))}");
//发送
await GoSendAsync(memory, cancellationToken).ConfigureAwait(false);
writer.Write(memory.Span);
}
/// <inheritdoc/>
protected override async Task PreviewSendAsync(IRequestInfo requestInfo, CancellationToken cancellationToken)
public override void SendInput<TWriter>(ref TWriter writer, IRequestInfo requestInfo)
{
if (!(requestInfo is ISendMessage sendMessage))
{
throw new Exception($"Unable to convert {nameof(requestInfo)} to {nameof(ISendMessage)}");
}
cancellationToken.ThrowIfCancellationRequested();
var byteBlock = new ValueByteBlock(sendMessage.MaxLength);
try
var span = writer.GetSpan(sendMessage.MaxLength);
sendMessage.Build(ref writer);
if (Logger?.LogLevel <= LogLevel.Trace)
{
sendMessage.Build(ref byteBlock);
if (Logger?.LogLevel <= LogLevel.Trace)
Logger?.Trace($"{ToString()}- Send:{(IsHexLog ? byteBlock.Span.ToHexString(' ') : (byteBlock.Span.ToString(Encoding.UTF8)))}");
//非并发主从协议
if (IsSingleThread)
{
SetRequest(sendMessage, ref byteBlock);
}
await GoSendAsync(byteBlock.Memory, cancellationToken).ConfigureAwait(false);
Logger?.Trace($"{ToString()}- Send:{(IsHexLog ? span.Slice(0, (int)writer.WrittenCount).ToHexString(' ') : (span.Slice(0, (int)writer.WrittenCount).ToString(Encoding.UTF8)))}");
}
finally
//非并发主从协议
if (IsSingleThread)
{
byteBlock.SafeDispose();
SetRequest(sendMessage);
}
}
}

View File

@@ -16,8 +16,10 @@ namespace ThingsGateway.Foundation;
/// <summary>
/// UDP适配器基类
/// </summary>
public class DeviceUdpDataHandleAdapter<TRequest> : UdpDataHandlingAdapter where TRequest : MessageBase, new()
public class DeviceUdpDataHandleAdapter<TRequest> : UdpDataHandlingAdapter, IDeviceDataHandleAdapter where TRequest : MessageBase, new()
{
public new ILog Logger { get; set; }
/// <inheritdoc/>
public override bool CanSendRequestInfo => true;
@@ -34,11 +36,11 @@ public class DeviceUdpDataHandleAdapter<TRequest> : UdpDataHandlingAdapter where
public TRequest Request { get; set; }
/// <inheritdoc />
public void SetRequest(ISendMessage sendMessage, ref ValueByteBlock byteBlock)
public void SetRequest(ISendMessage sendMessage)
{
var request = GetInstance();
request.Sign = sendMessage.Sign;
request.SendInfo(sendMessage, ref byteBlock);
request.SendInfo(sendMessage);
Request = request;
}
@@ -57,113 +59,132 @@ public class DeviceUdpDataHandleAdapter<TRequest> : UdpDataHandlingAdapter where
return new TRequest() { OperCode = -1, Sign = -1 };
}
/// <inheritdoc/>
protected override async Task PreviewReceived(EndPoint remoteEndPoint, IByteBlockReader byteBlock)
#region ParseRequest
/// <summary>
/// 尝试从字节读取器中解析出请求信息。
/// </summary>
/// <param name="remoteEndPoint">remoteEndPoint。</param>
/// <param name="memory">memory。</param>
/// <param name="request">解析出的请求信息。</param>
/// <returns>解析成功返回 true否则返回 false。</returns>
public bool TryParseRequest(EndPoint remoteEndPoint, ReadOnlyMemory<byte> memory, out TRequest request)
{
return this.ParseRequestCore(remoteEndPoint, memory, out request);
}
protected virtual bool ParseRequestCore(EndPoint remoteEndPoint, ReadOnlyMemory<byte> memory, out TRequest request1)
{
request1 = null;
try
{
byteBlock.Position = 0;
if (Logger?.LogLevel <= LogLevel.Trace)
Logger?.Trace($"{remoteEndPoint}- Receive:{(IsHexLog ? byteBlock.AsSegmentTake().ToHexString(' ') : byteBlock.ToString(byteBlock.Position))}");
Logger?.Trace($"{remoteEndPoint}- Receive:{(IsHexLog ? memory.Span.ToHexString(' ') : memory.Span.ToString(Encoding.UTF8))}");
TRequest request = null;
if (IsSingleThread)
request = Request == null ? GetInstance() : Request;
request = Request == null ? Request = GetInstance() : Request;
else
{
request = GetInstance();
}
request1 = request;
var pos = byteBlock.Position;
var byteBlock = new ByteBlockReader(memory);
byteBlock.BytesRead = 0;
var pos = byteBlock.BytesRead;
if (request.HeaderLength > byteBlock.CanReadLength)
{
return;//当头部都无法解析时,直接缓存
return false;//当头部都无法解析时,直接缓存
}
//检查头部合法性
if (request.CheckHead(ref byteBlock))
{
byteBlock.Position = pos;
byteBlock.BytesRead = pos;
if (request.BodyLength > MaxPackageSize)
{
OnError(default, $"Received BodyLength={request.BodyLength}, greater than the set MaxPackageSize={MaxPackageSize}", true, true);
return;
request.OperCode = -1;
request.ErrorMessage = $"Received BodyLength={request.BodyLength}, greater than the set MaxPackageSize={MaxPackageSize}";
Reset();
Logger?.LogWarning($"{ToString()} {request.ErrorMessage}");
return false;
}
if (request.BodyLength + request.HeaderLength > byteBlock.CanReadLength)
{
//body不满足解析开始缓存然后保存对象
return;
return false;
}
//if (request.BodyLength <= 0)
//{
// //如果body长度无法确定直接读取全部
// request.BodyLength = byteBlock.Length;
//}
var headPos = pos + request.HeaderLength;
byteBlock.Position = headPos;
byteBlock.BytesRead = 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}");
Logger?.Trace($"{ToString()}-Received incomplete, cached message, need length:{request.HeaderLength + request.BodyLength} ,current length:{byteBlock.BytesRead + byteBlock.BytesRemaining} {request?.ErrorMessage}");
request.OperCode = -1;
}
else if (result == FilterResult.GoOn)
{
var addLen = request.HeaderLength + request.BodyLength;
byteBlock.Position = pos + (addLen > 0 ? addLen : 1);
Logger?.Trace($"{ToString()}-{request?.ToString()}");
if (Logger?.LogLevel <= LogLevel.Trace)
Logger?.Trace($"{ToString()}-{request?.ToString()}");
request.OperCode = -1;
if ((Owner as IClientChannel)?.WaitHandlePool?.TryGetDataAsync(request.Sign, out var waitDataAsync) == true)
{
waitDataAsync.SetResult(request);
}
}
else if (result == FilterResult.Success)
{
var addLen = request.HeaderLength + request.BodyLength;
byteBlock.Position = pos + (addLen > 0 ? addLen : 1);
await GoReceived(remoteEndPoint, null, request).ConfigureAwait(false);
request1 = request;
return true;
}
return;
}
else
{
byteBlock.Position = pos + 1;
request.OperCode = -1;
return;
return false;
}
return false;
}
catch (Exception ex)
{
Logger?.LogWarning(ex, $"{ToString()} Received parsing error");
byteBlock.Position = byteBlock.Length;//移动游标
return;
return false;
}
}
#endregion
/// <inheritdoc/>
protected override async Task PreviewSendAsync(EndPoint endPoint, ReadOnlyMemory<byte> memory, CancellationToken cancellationToken)
protected override Task PreviewReceivedAsync(EndPoint remoteEndPoint, ReadOnlyMemory<byte> memory)
{
if (ParseRequestCore(remoteEndPoint, memory, out var request))
{
return GoReceived(remoteEndPoint, null, request);
}
return EasyTask.CompletedTask;
}
/// <inheritdoc/>
protected override Task PreviewSendAsync(EndPoint endPoint, ReadOnlyMemory<byte> memory, CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
if (Logger?.LogLevel <= LogLevel.Trace)
Logger?.Trace($"{ToString()}- Send:{(IsHexLog ? memory.Span.ToHexString(' ') : (memory.Span.ToString(Encoding.UTF8)))}");
//发送
await GoSendAsync(endPoint, memory, cancellationToken).ConfigureAwait(false);
return GoSendAsync(endPoint, memory, cancellationToken);
}
/// <inheritdoc/>
protected override async Task PreviewSendAsync(EndPoint endPoint, IRequestInfo requestInfo, CancellationToken cancellationToken)
protected override Task PreviewSendAsync(EndPoint endPoint, IRequestInfo requestInfo, CancellationToken cancellationToken)
{
if (!(requestInfo is ISendMessage sendMessage))
{
throw new Exception($"Unable to convert {nameof(requestInfo)} to {nameof(ISendMessage)}");
}
cancellationToken.ThrowIfCancellationRequested();
var byteBlock = new ValueByteBlock(sendMessage.MaxLength);
try
{
@@ -173,9 +194,9 @@ public class DeviceUdpDataHandleAdapter<TRequest> : UdpDataHandlingAdapter where
if (IsSingleThread)
{
SetRequest(sendMessage, ref byteBlock);
SetRequest(sendMessage);
}
await GoSendAsync(endPoint, byteBlock.Memory, cancellationToken).ConfigureAwait(false);
return GoSendAsync(endPoint, byteBlock.Memory, cancellationToken);
}
finally
{

View File

@@ -1,46 +0,0 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人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 IByteBlockWriterBuilder
{
/// <summary>
/// 构建数据时,指示内存池的申请长度。
/// <para>
/// 建议:该值可以尽可能的设置大一些,这样可以避免内存池扩容。
/// </para>
/// </summary>
int MaxLength { get; }
/// <summary>
/// 构建对象到<see cref="ByteBlock"/>
/// </summary>
/// <param name="writer">要构建的字节块对象引用。</param>
void Build<TWriter>(ref TWriter writer) where TWriter : IByteBlockWriter
#if AllowsRefStruct
,allows ref struct
#endif
;
}
/// <summary>
/// 指示<see cref="IRequestInfo"/>应当如何构建
/// </summary>
public interface IRequestInfoByteBlockWriterBuilder : IRequestInfo, IByteBlockWriterBuilder
{
}

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.Foundation
{
public interface IDeviceDataHandleAdapter
{
bool CanSendRequestInfo { get; }
bool IsHexLog { get; set; }
bool IsSingleThread { get; set; }
ILog Logger { get; set; }
}
}

View File

@@ -18,7 +18,7 @@ public interface IResultMessage : IOperResult, IRequestInfo
/// <summary>
/// 数据体长度
/// </summary>
int BodyLength { get; set; }
long BodyLength { get; set; }
/// <summary>
/// 解析的字节信息
@@ -28,7 +28,7 @@ public interface IResultMessage : IOperResult, IRequestInfo
/// <summary>
/// 消息头的指令长度,不固定时返回0
/// </summary>
int HeaderLength { get; }
long HeaderLength { get; }
/// <summary>
/// 等待标识,对于并发协议,必须从协议中例如固定头部获取标识字段
@@ -42,17 +42,17 @@ public interface IResultMessage : IOperResult, IRequestInfo
/// <para>然后返回<see cref="FilterResult.GoOn"/></para>
/// </summary>
/// <returns>是否成功有效</returns>
FilterResult CheckBody<TByteBlock>(ref TByteBlock byteBlock) where TByteBlock : IByteBlockReader;
FilterResult CheckBody<TByteBlock>(ref TByteBlock byteBlock) where TByteBlock : IBytesReader;
/// <summary>
/// 检查头子节的合法性,并赋值<see cref="BodyLength"/><br />
/// <para>如果返回false意味着放弃本次解析的所有数据包括已经解析完成的Header</para>
/// </summary>
/// <returns>是否成功的结果</returns>
bool CheckHead<TByteBlock>(ref TByteBlock byteBlock) where TByteBlock : IByteBlockReader;
bool CheckHead<TByteBlock>(ref TByteBlock byteBlock) where TByteBlock : IBytesReader;
/// <summary>
/// 发送前的信息处理,例如存储某些特征信息:站号/功能码等等用于验证后续的返回信息是否合法
/// </summary>
void SendInfo(ISendMessage sendMessage, ref ValueByteBlock byteBlock);
void SendInfo(ISendMessage sendMessage);
}

View File

@@ -13,6 +13,6 @@ namespace ThingsGateway.Foundation;
/// <summary>
/// 发送消息
/// </summary>
public interface ISendMessage : IRequestInfo, IWaitHandle, IRequestInfoByteBlockWriterBuilder
public interface ISendMessage : IRequestInfo, IWaitHandle, IRequestInfoBuilder
{
}

View File

@@ -33,28 +33,28 @@ public class MessageBase : OperResultClass<ReadOnlyMemory<byte>>, IResultMessage
#endregion
/// <inheritdoc/>
public int BodyLength { get; set; }
public long BodyLength { get; set; }
/// <inheritdoc/>
public virtual int HeaderLength { get; set; }
public virtual long HeaderLength { get; set; }
/// <inheritdoc/>
public virtual int Sign { get; set; } = -1;
/// <inheritdoc />
public virtual FilterResult CheckBody<TByteBlock>(ref TByteBlock byteBlock) where TByteBlock : IByteBlockReader
public virtual FilterResult CheckBody<TByteBlock>(ref TByteBlock byteBlock) where TByteBlock : IBytesReader
{
return FilterResult.Success;
}
/// <inheritdoc/>
public virtual bool CheckHead<TByteBlock>(ref TByteBlock byteBlock) where TByteBlock : IByteBlockReader
public virtual bool CheckHead<TByteBlock>(ref TByteBlock byteBlock) where TByteBlock : IBytesReader
{
return true;
}
/// <inheritdoc/>
public virtual void SendInfo(ISendMessage sendMessage, ref TouchSocket.Core.ValueByteBlock byteBlock)
public virtual void SendInfo(ISendMessage sendMessage)
{
}
}

View File

@@ -1,306 +0,0 @@
//------------------------------------------------------------------------------
// 此代码版权除特别声明或在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首页https://touchsocket.net/
// 交流QQ群234762506
// 感谢您的下载和使用
//------------------------------------------------------------------------------
namespace ThingsGateway.Foundation;
/// <summary>
/// 用户自定义数据处理适配器,使用该适配器时,接收方收到的数据中,<see cref="ByteBlock"/>将为<see langword="null"/>
/// 同时<see cref="IRequestInfo"/>将实现为TRequest发送数据直接发送。
/// </summary>
public abstract class TcpCustomDataHandlingAdapter<TRequest> : SingleStreamDataHandlingAdapter where TRequest : IRequestInfo
{
private readonly Type m_requestType;
private ValueByteBlock m_tempByteBlock;
private TRequest m_tempRequest;
/// <summary>
/// 初始化自定义数据处理适配器。
/// </summary>
/// <remarks>
/// 该构造函数在创建<see cref="TcpCustomDataHandlingAdapter{TRequest}"/>实例时,会指定请求类型。
/// </remarks>
public TcpCustomDataHandlingAdapter()
{
this.m_requestType = typeof(TRequest);
}
/// <inheritdoc/>
public override bool CanSendRequestInfo => false;
/// <summary>
/// 指示需要解析当前包的剩余长度。如果不能得知,请赋值<see cref="int.MaxValue"/>。
/// </summary>
protected int SurLength { get; set; }
/// <summary>
/// 尝试解析请求数据块。
/// </summary>
/// <typeparam name="TByteBlock">字节块类型必须实现IByteBlock接口。</typeparam>
/// <param name="reader">待解析的字节块。</param>
/// <param name="request">解析出的请求对象。</param>
/// <returns>解析是否成功。</returns>
public bool TryParseRequest<TByteBlock>(ref TByteBlock reader, out TRequest request) where TByteBlock : IByteBlockReader
{
// 检查缓存是否超时,如果超时则清除缓存。
if (this.CacheTimeoutEnable && DateTimeOffset.UtcNow - this.LastCacheTime > this.CacheTimeout)
{
this.Reset();
}
// 如果临时字节块为空,则尝试直接解析。
if (this.m_tempByteBlock.IsEmpty)
{
return this.Single(ref reader, out request) == FilterResult.Success;
}
else
{
// 如果剩余长度小于等于0则抛出异常。
if (this.SurLength <= 0)
{
throw new Exception();
}
// 计算本次可以读取的长度。
var len = Math.Min(this.SurLength, reader.CanReadLength);
// 从输入字节块中读取数据到临时字节块中。
var block = this.m_tempByteBlock;
block.Write(reader.Span.Slice(reader.Position, len));
reader.Position += len;
this.SurLength -= len;
// 重置临时字节块并准备下一次使用。
this.m_tempByteBlock = default;
// 回到字节块的起始位置。
block.SeekToStart();
try
{
// 尝试解析字节块。
var filterResult = this.Single(ref block, out request);
switch (filterResult)
{
case FilterResult.Cache:
{
// 如果临时字节块不为空,则继续缓存。
if (!this.m_tempByteBlock.IsEmpty)
{
reader.Position += this.m_tempByteBlock.Length;
}
return false;
}
case FilterResult.Success:
{
// 如果字节块中还有剩余数据,则回退指针。
if (block.CanReadLength > 0)
{
reader.Position -= block.CanReadLength;
}
return true;
}
case FilterResult.GoOn:
default:
// 对于需要继续解析的情况,也回退指针。
if (block.CanReadLength > 0)
{
reader.Position -= block.CanReadLength;
}
return false;
}
}
finally
{
// 释放字节块资源。
block.Dispose();
}
}
}
/// <summary>
/// 筛选解析数据。实例化的TRequest会一直保存直至解析成功或手动清除。
/// <para>当不满足解析条件时,请返回<see cref="FilterResult.Cache"/>,此时会保存<see cref="ByteBlock.CanReadLength"/>的数据</para>
/// <para>当数据部分异常时,请移动<see cref="ByteBlock.Position"/>到指定位置,然后返回<see cref="FilterResult.GoOn"/></para>
/// <para>当完全满足解析条件时,请返回<see cref="FilterResult.Success"/>最后将<see cref="ByteBlock.Position"/>移至指定位置。</para>
/// </summary>
/// <param name="reader">字节块</param>
/// <param name="beCached">是否为上次遗留对象,当该参数为<see langword="true"/>时request也将是上次实例化的对象。</param>
/// <param name="request">对象。</param>
/// <param name="tempCapacity">缓存容量。当需要首次缓存时指示申请的ByteBlock的容量。合理的值可避免ByteBlock扩容带来的性能消耗。</param>
/// <returns></returns>
protected abstract FilterResult Filter<TByteBlock>(ref TByteBlock reader, bool beCached, ref TRequest request, ref int tempCapacity)
where TByteBlock : IByteBlockReader;
/// <summary>
/// 判断请求对象是否应该被缓存。
/// </summary>
/// <param name="request">请求对象。</param>
/// <returns>返回布尔值,指示请求对象是否应该被缓存。</returns>
protected virtual bool IsBeCached(in TRequest request)
{
return this.m_requestType.IsValueType ? request.GetHashCode() != default(TRequest).GetHashCode() : request != null;
}
/// <summary>
/// 成功执行接收以后。
/// </summary>
/// <param name="request"></param>
protected virtual void OnReceivedSuccess(TRequest request)
{
}
/// <summary>
/// 即将执行<see cref="SingleStreamDataHandlingAdapter.GoReceivedAsync(IByteBlockReader, IRequestInfo)"/>。
/// </summary>
/// <param name="request"></param>
/// <returns>返回值标识是否继续执行</returns>
protected virtual bool OnReceivingSuccess(TRequest request)
{
return true;
}
/// <inheritdoc/>
/// <param name="byteBlock"></param>
protected override async Task PreviewReceivedAsync(IByteBlockReader byteBlock)
{
if (this.CacheTimeoutEnable && DateTimeOffset.UtcNow - this.LastCacheTime > this.CacheTimeout)
{
this.Reset();
}
if (this.m_tempByteBlock.IsEmpty)
{
await this.SingleAsync(byteBlock).ConfigureAwait(EasyTask.ContinueOnCapturedContext);
}
else
{
this.m_tempByteBlock.Write(byteBlock.Span);
using (var block = this.m_tempByteBlock)
{
this.m_tempByteBlock = default;
await this.SingleAsync(block).ConfigureAwait(EasyTask.ContinueOnCapturedContext);
}
}
}
/// <inheritdoc/>
protected override void Reset()
{
this.m_tempByteBlock.SafeDispose();
this.m_tempByteBlock = default;
this.m_tempRequest = default;
this.SurLength = 0;
base.Reset();
}
/// <summary>
/// 处理单个字节块,提取请求对象。
/// </summary>
/// <typeparam name="TByteBlock">字节块类型需要实现IByteBlock接口。</typeparam>
/// <param name="reader">字节块,将被解析以提取请求对象。</param>
/// <param name="request">输出参数,提取出的请求对象。</param>
/// <returns>返回过滤结果,指示处理的状态。</returns>
protected FilterResult Single<TByteBlock>(ref TByteBlock reader, out TRequest request) where TByteBlock : IByteBlockReader
{
// 初始化临时缓存容量。
var tempCapacity = 1024 * 64;
// 执行过滤操作,根据是否应该缓存来决定如何处理字节块和请求对象。
var filterResult = this.Filter(ref reader, this.IsBeCached(this.m_tempRequest), ref this.m_tempRequest, ref tempCapacity);
switch (filterResult)
{
case FilterResult.Success:
// 如果过滤结果是成功,则设置请求对象并重置临时请求对象为默认值。
request = this.m_tempRequest;
this.m_tempRequest = default;
return filterResult;
case FilterResult.Cache:
// 如果过滤结果需要缓存,则创建一个新的字节块来缓存数据。
if (reader.CanReadLength > 0)
{
this.m_tempByteBlock = new ValueByteBlock(tempCapacity);
this.m_tempByteBlock.Write(reader.Span.Slice(reader.Position, reader.CanReadLength));
// 如果缓存的数据长度超过设定的最大包大小,则抛出异常。
if (this.m_tempByteBlock.Length > this.MaxPackageSize)
{
throw new Exception($"The parsed signal was not received when the cached data length {m_tempByteBlock.Length} exceeds the set value {MaxPackageSize}");
}
// 将字节块指针移到末尾。
reader.Advance((int)reader.BytesRemaining);
}
// 更新缓存时间。
if (this.UpdateCacheTimeWhenRev)
{
this.LastCacheTime = DateTimeOffset.UtcNow;
}
request = default;
return filterResult;
case FilterResult.GoOn:
default:
// 对于继续或默认的过滤结果,更新缓存时间。
if (this.UpdateCacheTimeWhenRev)
{
this.LastCacheTime = DateTimeOffset.UtcNow;
}
request = default;
return filterResult;
}
}
private async Task SingleAsync<TByteBlock>(TByteBlock reader) where TByteBlock : IByteBlockReader
{
reader.Position = 0;
while (reader.Position < reader.Length)
{
if (this.DisposedValue)
{
return;
}
var tempCapacity = 1024 * 64;
var filterResult = this.Filter(ref reader, this.IsBeCached(this.m_tempRequest), ref this.m_tempRequest, ref tempCapacity);
switch (filterResult)
{
case FilterResult.Success:
if (this.OnReceivingSuccess(this.m_tempRequest))
{
await this.GoReceivedAsync(null, this.m_tempRequest).ConfigureAwait(EasyTask.ContinueOnCapturedContext);
this.OnReceivedSuccess(this.m_tempRequest);
}
this.m_tempRequest = default;
break;
case FilterResult.Cache:
this.m_tempByteBlock = new ValueByteBlock(tempCapacity);
this.m_tempByteBlock.Write(reader.Span);
if (this.m_tempByteBlock.Length > this.MaxPackageSize)
{
this.OnError(default, $"The parsed signal was not received when the cached data length {m_tempByteBlock.Length} exceeds the set value {MaxPackageSize}", true, true);
}
if (this.UpdateCacheTimeWhenRev)
{
this.LastCacheTime = DateTimeOffset.UtcNow;
}
return;
case FilterResult.GoOn:
if (this.UpdateCacheTimeWhenRev)
{
this.LastCacheTime = DateTimeOffset.UtcNow;
}
break;
}
}
}
}

View File

@@ -214,10 +214,7 @@ public abstract class DeviceBase : AsyncAndSyncDisposableObject, IDevice
/// </summary>
protected virtual ValueTask<bool> ChannelStarting(IClientChannel channel, bool last)
{
if (channel.ReadOnlyDataHandlingAdapter != null)
{
channel.ReadOnlyDataHandlingAdapter.Logger = Logger;
}
channel.SetDataHandlingAdapterLogger(Logger);
return EasyValueTask.FromResult(true);
}
@@ -318,7 +315,7 @@ public abstract class DeviceBase : AsyncAndSyncDisposableObject, IDevice
{
try
{
if (client.WaitHandlePool.SetRun(response))
if (client.WaitHandlePool.Set(response))
{
e.Handled = true;
}
@@ -366,21 +363,13 @@ public abstract class DeviceBase : AsyncAndSyncDisposableObject, IDevice
}
}
private async ValueTask BefortSendAsync(IClientChannel channel, CancellationToken token)
private Task BefortSendAsync(IClientChannel channel, CancellationToken token)
{
SetDataAdapter(channel);
try
{
await ConnectAsync(token).ConfigureAwait(false);
}
catch (Exception)
{
await Task.Delay(1000, token).ConfigureAwait(false);
throw;
}
if (token.IsCancellationRequested)
throw new OperationCanceledException();
return ConnectAsync(token);
}
private WaitLock connectWaitLock = new(nameof(DeviceBase));
@@ -397,13 +386,13 @@ public abstract class DeviceBase : AsyncAndSyncDisposableObject, IDevice
if (Channel.PluginManager == null)
await Channel.SetupAsync(Channel.Config.Clone()).ConfigureAwait(false);
await Channel.CloseAsync().ConfigureAwait(false);
await Task.Delay(500, token).ConfigureAwait(false);
await Channel.ConnectAsync(Channel.ChannelOptions.ConnectTimeout, token).ConfigureAwait(false);
using var ctsTime = new CancellationTokenSource(Channel.ChannelOptions.ConnectTimeout);
using var cts = CancellationTokenSource.CreateLinkedTokenSource(ctsTime.Token, token);
await Channel.ConnectAsync(cts.Token).ConfigureAwait(false);
}
}
finally
{
try { await Task.Delay(500, token).ConfigureAwait(false); } catch { }
connectWaitLock.Release();
}
}
@@ -424,8 +413,7 @@ public abstract class DeviceBase : AsyncAndSyncDisposableObject, IDevice
await BefortSendAsync(channelResult.Content, cancellationToken).ConfigureAwait(false);
await waitLock.WaitAsync(cancellationToken).ConfigureAwait(false);
if (channelResult.Content.ReadOnlyDataHandlingAdapter != null)
channelResult.Content.ReadOnlyDataHandlingAdapter.Logger = Logger;
channelResult.Content.SetDataHandlingAdapterLogger(Logger);
EndPoint? endPoint = GetUdpEndpoint(dtuId);
@@ -438,6 +426,8 @@ public abstract class DeviceBase : AsyncAndSyncDisposableObject, IDevice
}
catch (Exception ex)
{
if (!cancellationToken.IsCancellationRequested)
await Task.Delay(1000, cancellationToken).ConfigureAwait(false);
return new(ex);
}
}
@@ -513,8 +503,6 @@ public abstract class DeviceBase : AsyncAndSyncDisposableObject, IDevice
return SendThenReturnAsync(sendMessage, channelResult.Content, cancellationToken);
}
/// <inheritdoc/>
public virtual async ValueTask<OperResult<ReadOnlyMemory<byte>>> SendThenReturnAsync(ISendMessage sendMessage, IClientChannel channel, CancellationToken cancellationToken = default)
{
@@ -530,11 +518,11 @@ public abstract class DeviceBase : AsyncAndSyncDisposableObject, IDevice
}
/// <inheritdoc/>
protected virtual async ValueTask<MessageBase> SendThenReturnMessageAsync(ISendMessage sendMessage, CancellationToken cancellationToken = default)
protected virtual ValueTask<MessageBase> SendThenReturnMessageAsync(ISendMessage sendMessage, CancellationToken cancellationToken = default)
{
var channelResult = GetChannel(this is IDtu dtu ? dtu.DtuId : null);
if (!channelResult.IsSuccess) return new MessageBase(channelResult);
return await SendThenReturnMessageAsync(sendMessage, channelResult.Content, cancellationToken).ConfigureAwait(false);
if (!channelResult.IsSuccess) return EasyValueTask.FromResult(new MessageBase(channelResult));
return SendThenReturnMessageAsync(sendMessage, channelResult.Content, cancellationToken);
}
/// <inheritdoc/>
@@ -565,10 +553,8 @@ public abstract class DeviceBase : AsyncAndSyncDisposableObject, IDevice
if (cancellationToken.IsCancellationRequested)
return new MessageBase(new OperationCanceledException());
if (clientChannel.ReadOnlyDataHandlingAdapter != null)
clientChannel.ReadOnlyDataHandlingAdapter.Logger = Logger;
clientChannel.SetDataHandlingAdapterLogger(Logger);
Channel.ChannelReceivedWaitDict.TryAdd(sign, ChannelReceived);
if (cancellationToken.IsCancellationRequested)
return new MessageBase(new OperationCanceledException());
@@ -577,20 +563,29 @@ public abstract class DeviceBase : AsyncAndSyncDisposableObject, IDevice
if (!sendOperResult.IsSuccess)
return new MessageBase(sendOperResult);
using var ctsTime = new CancellationTokenSource(timeout);
try
{
waitData.SetCancellationToken(Channel.ClosedToken);
await waitData.WaitAsync(timeout).ConfigureAwait(false);
using var cts = CancellationTokenSource.CreateLinkedTokenSource(ctsTime.Token, Channel.ClosedToken);
await waitData.WaitAsync(cts.Token).ConfigureAwait(false);
}
catch (OperationCanceledException)
{
if (ctsTime.IsCancellationRequested)
{
return new MessageBase(new TimeoutException());
}
}
catch (Exception ex)
{
return new MessageBase(ex);
}
var result = waitData.Check();
var result = waitData.Check(ctsTime.Token);
if (result.IsSuccess)
{
return waitData.WaitResult;
return waitData.CompletedData;
}
else
{
@@ -599,13 +594,14 @@ public abstract class DeviceBase : AsyncAndSyncDisposableObject, IDevice
}
catch (Exception ex)
{
if (!cancellationToken.IsCancellationRequested)
await Task.Delay(1000, cancellationToken).ConfigureAwait(false);
return new MessageBase(ex);
}
finally
{
Channel.ChannelReceivedWaitDict.TryRemove(sign, out _);
waitLock?.Release();
clientChannel.WaitHandlePool.Destroy(sign);
waitData?.SafeDispose();
}
}
@@ -964,10 +960,10 @@ public abstract class DeviceBase : AsyncAndSyncDisposableObject, IDevice
}
/// <inheritdoc/>
public virtual async ValueTask<OperResult> WriteAsync(string address, ReadOnlyMemory<string> value, IThingsGatewayBitConverter bitConverter = null, CancellationToken cancellationToken = default)
public virtual ValueTask<OperResult> WriteAsync(string address, ReadOnlyMemory<string> value, IThingsGatewayBitConverter bitConverter = null, CancellationToken cancellationToken = default)
{
bitConverter ??= ThingsGatewayBitConverter.GetTransByAddress(address);
if (bitConverter.StringLength == null) return new OperResult(AppResource.StringAddressError);
if (bitConverter.StringLength == null) return EasyValueTask.FromResult(new OperResult(AppResource.StringAddressError));
List<ReadOnlyMemory<byte>> bytes = new();
foreach (var a in value.Span)
{
@@ -975,7 +971,7 @@ public abstract class DeviceBase : AsyncAndSyncDisposableObject, IDevice
bytes.Add(data.ArrayExpandToLength(bitConverter.StringLength ?? data.Length));
}
return await WriteAsync(address, bytes.CombineMemoryBlocks(), DataTypeEnum.String, cancellationToken).ConfigureAwait(false);
return WriteAsync(address, bytes.CombineMemoryBlocks(), DataTypeEnum.String, cancellationToken);
}
#endregion
@@ -997,7 +993,7 @@ public abstract class DeviceBase : AsyncAndSyncDisposableObject, IDevice
{
if (Channel is ITcpServiceChannel tcpServiceChannel)
{
tcpServiceChannel.Clients.ForEach(a => a.WaitHandlePool.SafeDispose());
tcpServiceChannel.Clients.ForEach(a => a.WaitHandlePool?.CancelAll());
}
try
@@ -1006,7 +1002,7 @@ public abstract class DeviceBase : AsyncAndSyncDisposableObject, IDevice
_ = Channel.CloseAsync();
if (Channel is IClientChannel client)
{
client.WaitHandlePool.SafeDispose();
client.WaitHandlePool?.CancelAll();
}
}
catch (Exception ex)
@@ -1020,7 +1016,7 @@ public abstract class DeviceBase : AsyncAndSyncDisposableObject, IDevice
{
if (tcpServiceChannel.TryGetClient($"ID={dtu.DtuId}", out var client))
{
client.WaitHandlePool?.SafeDispose();
client.WaitHandlePool?.CancelAll();
_ = client.CloseAsync();
}
}
@@ -1049,7 +1045,7 @@ public abstract class DeviceBase : AsyncAndSyncDisposableObject, IDevice
{
if (Channel is ITcpServiceChannel tcpServiceChannel)
{
tcpServiceChannel.Clients.ForEach(a => a.WaitHandlePool.SafeDispose());
tcpServiceChannel.Clients.ForEach(a => a.WaitHandlePool?.CancelAll());
}
try
@@ -1058,7 +1054,7 @@ public abstract class DeviceBase : AsyncAndSyncDisposableObject, IDevice
await Channel.CloseAsync().ConfigureAwait(false);
if (Channel is IClientChannel client)
{
client.WaitHandlePool.SafeDispose();
client.WaitHandlePool?.CancelAll();
}
}
catch (Exception ex)
@@ -1072,7 +1068,7 @@ public abstract class DeviceBase : AsyncAndSyncDisposableObject, IDevice
{
if (tcpServiceChannel.TryGetClient($"ID={dtu.DtuId}", out var client))
{
client.WaitHandlePool?.SafeDispose();
client.WaitHandlePool?.CancelAll();
await client.CloseAsync().ConfigureAwait(false);
}
}

View File

@@ -132,24 +132,43 @@ public static partial class DeviceExtension
}
/// <summary>
/// 当状态不是<see cref="WaitDataStatus.SetRunning"/>时返回异常。
/// 当状态不是<see cref="WaitDataStatus.Success"/>时返回异常。
/// </summary>
public static OperResult Check(this WaitDataAsync<MessageBase> waitDataAsync)
public static OperResult Check(this AsyncWaitData<MessageBase> waitDataAsync, CancellationToken cancellationToken)
{
switch (waitDataAsync.Status)
{
case WaitDataStatus.SetRunning:
case WaitDataStatus.Success:
return new();
case WaitDataStatus.Canceled: return new(new OperationCanceledException());
case WaitDataStatus.Canceled:
if (cancellationToken.IsCancellationRequested)
{
if (waitDataAsync.CompletedData != null)
{
waitDataAsync.CompletedData.Exception = new TimeoutException();
if (waitDataAsync.CompletedData.IsSuccess) waitDataAsync.CompletedData.OperCode = 999;
if (waitDataAsync.CompletedData.ErrorMessage.IsNullOrEmpty()) waitDataAsync.CompletedData.ErrorMessage = "Timeout";
return new(waitDataAsync.CompletedData);
}
else
{
return new(new TimeoutException());
}
}
else
{
return new(new OperationCanceledException());
}
case WaitDataStatus.Overtime:
{
if (waitDataAsync.WaitResult != null)
if (waitDataAsync.CompletedData != null)
{
waitDataAsync.WaitResult.Exception = new TimeoutException();
if (waitDataAsync.WaitResult.IsSuccess) waitDataAsync.WaitResult.OperCode = 999;
if (waitDataAsync.WaitResult.ErrorMessage.IsNullOrEmpty()) waitDataAsync.WaitResult.ErrorMessage = "Timeout";
return new(waitDataAsync.WaitResult);
waitDataAsync.CompletedData.Exception = new TimeoutException();
if (waitDataAsync.CompletedData.IsSuccess) waitDataAsync.CompletedData.OperCode = 999;
if (waitDataAsync.CompletedData.ErrorMessage.IsNullOrEmpty()) waitDataAsync.CompletedData.ErrorMessage = "Timeout";
return new(waitDataAsync.CompletedData);
}
else
{
@@ -160,7 +179,7 @@ public static partial class DeviceExtension
case WaitDataStatus.Default:
default:
{
return waitDataAsync.WaitResult == null ? new(new Exception(AppResource.UnknownError)) : new(waitDataAsync.WaitResult);
return waitDataAsync.CompletedData == null ? new(new Exception(AppResource.UnknownError)) : new(waitDataAsync.CompletedData);
}
}
}

View File

@@ -9,6 +9,7 @@
//------------------------------------------------------------------------------
using System.Buffers;
using System.Runtime.CompilerServices;
using System.Text;
namespace ThingsGateway.Foundation;
@@ -20,70 +21,8 @@ namespace ThingsGateway.Foundation;
/// </summary>
public static class ByteBlockExtension
{
public static void WriteBackAddValue<TWriter>(ref TWriter writer, byte value, int pos)
where TWriter : IByteBlockWriter
{
int nowPos = writer.Position;
writer.Position = pos;
WriterExtension.WriteValue(ref writer, (byte)(writer.Span[pos] + value));
writer.Position = nowPos;
}
public static void WriteBackValue<TWriter, T>(ref TWriter writer, T value, int pos)
where T : unmanaged
where TWriter : IByteBlockWriter
{
int nowPos = writer.Position;
writer.Position = pos;
WriterExtension.WriteValue(ref writer, value);
writer.Position = nowPos;
}
public static void WriteBackValue<TWriter, T>(ref TWriter writer, T value, EndianType endianType, int pos)
where T : unmanaged
where TWriter : IByteBlockWriter
{
int nowPos = writer.Position;
writer.Position = pos;
WriterExtension.WriteValue(ref writer, value, endianType);
writer.Position = nowPos;
}
public static void WriteBackNormalString<TWriter>(ref TWriter writer, string value, Encoding encoding, int pos)
where TWriter : IByteBlockWriter
{
int nowPos = writer.Position;
writer.Position = pos;
WriterExtension.WriteNormalString(ref writer, value, encoding);
writer.Position = nowPos;
}
public static string ReadNormalString<TReader>(ref TReader reader, int length)
where TReader : IBytesReader
{
var span = reader.GetSpan(length).Slice(0, length);
var str = span.ToString(Encoding.UTF8);
reader.Advance(length);
return str;
}
/// <summary>
/// 将值类型的字节块转换为普通的字节块。
/// </summary>
/// <param name="valueByteBlock">要转换的值类型字节块。</param>
/// <returns>一个新的字节块对象。</returns>
public static ByteBlock AsByteBlock(this ValueByteBlock valueByteBlock)
{
ByteBlock byteBlock = new ByteBlock(valueByteBlock.TotalMemory.Slice(0, valueByteBlock.Length));
byteBlock.Position = valueByteBlock.Position;
byteBlock.SetLength(valueByteBlock.Length);
return byteBlock;
}
#region ToArray
/// <summary>
@@ -259,5 +198,252 @@ public static class ByteBlockExtension
return ToString(byteBlock, offset, byteBlock.BytesRead + byteBlock.BytesRemaining - offset);
}
/// <inheritdoc/>
public static string ToString<TByteBlock>(this TByteBlock byteBlock) where TByteBlock : IBytesReader
{
return byteBlock.TotalSequence.ToString(Encoding.UTF8);
}
#endregion ToString
#region ToHexString
public static string ToHexString<TByteBlock>(this TByteBlock byteBlock, long offset, long length, char splite = default) where TByteBlock : IBytesReader
{
return byteBlock.TotalSequence.Slice(offset, length).ToHexString(Encoding.UTF8, splite);
}
public static string ToHexString(this ReadOnlySequence<byte> byteBlock, Encoding encoding, char splite = default)
{
return byteBlock.ToHexString(splite);
}
/// <inheritdoc/>
public static string ToHexString<TByteBlock>(this TByteBlock byteBlock, long offset, char splite = default) where TByteBlock : IBytesReader
{
return ToHexString(byteBlock, offset, byteBlock.BytesRead + byteBlock.BytesRemaining - offset, splite);
}
/// <inheritdoc/>
public static string ToHexString<TByteBlock>(this TByteBlock byteBlock, char splite = default) where TByteBlock : IBytesReader
{
return byteBlock.TotalSequence.ToHexString(Encoding.UTF8, splite);
}
#endregion ToHexString
/// <summary>
/// 在 <see cref="ReadOnlySequence{T}"/> 中查找第一个与指定 <see cref="byte"/> 匹配的子序列的起始索引。
/// <para>如果未找到则返回 -1。</para>
/// </summary>
/// <param name="sequence">要搜索的字节序列。</param>
/// <param name="firstByte">要查找的字节。</param>
/// <returns>匹配子序列的起始索引,未找到则返回 -1。</returns>
public static long IndexOf(this ReadOnlySequence<byte> sequence, byte firstByte)
{
if (sequence.Length < 1)
{
return -1;
}
long globalPosition = 0;
var enumerator = sequence.GetEnumerator();
// 遍历每个内存段
while (enumerator.MoveNext())
{
var currentSpan = enumerator.Current.Span;
var localIndex = 0;
// 在当前段中搜索首字节
while (localIndex < currentSpan.Length)
{
// 查找首字节匹配位置
var matchIndex = currentSpan.Slice(localIndex).IndexOf(firstByte);
if (matchIndex == -1)
{
break;
}
localIndex += matchIndex;
var globalIndex = globalPosition + localIndex;
// 检查剩余长度是否足够
if (sequence.Length - globalIndex < 1)
{
return -1;
}
// 检查完整匹配
if (IsMatch(sequence, globalIndex, firstByte))
{
return globalIndex;
}
localIndex++; // 继续搜索下一个位置
}
globalPosition += currentSpan.Length;
}
return -1;
}
private static bool IsMatch(ReadOnlySequence<byte> sequence, long start, byte value)
{
return sequence.Slice(start, 1).First.Span[0] == value;
}
public static byte GetByte(this ReadOnlySequence<byte> sequence, long index)
{
return sequence.Slice(index).First.Span[0];
}
/// <summary>
/// 计算指定区间字节的和
/// </summary>
public static long SumRange(this ReadOnlySequence<byte> sequence, long start, long count)
{
if (start < 0 || count < 0 || start + count > sequence.Length)
throw new ArgumentOutOfRangeException();
long sum = 0;
long remaining = count;
foreach (var segment in sequence)
{
if (start >= segment.Length)
{
// 起点不在当前段
start -= segment.Length;
continue;
}
var span = segment.Span;
int take = (int)Math.Min(segment.Length - start, remaining);
for (int i = 0; i < take; i++)
sum += span[(int)start + i];
remaining -= take;
start = 0; // 后续段从头开始
if (remaining == 0)
break;
}
return sum;
}
/// <summary>
/// 比较 ReadOnlySequence 与 ReadOnlySpan 的内容是否一致。
/// </summary>
public static bool SequenceEqual<T>(this ReadOnlySequence<T> sequence, ReadOnlySpan<T> other)
where T : IEquatable<T>
{
if (sequence.Length != other.Length)
return false;
// 单段,直接比较
if (sequence.IsSingleSegment)
{
return sequence.First.Span.SequenceEqual(other);
}
// 多段,逐段比较
int offset = 0;
foreach (var segment in sequence)
{
var span = segment.Span;
if (!other.Slice(offset, span.Length).SequenceEqual(span))
return false;
offset += span.Length;
}
return true;
}
/// <summary>
/// 将指定的字节块从当前位置<see cref="IByteBlockCore.Position"/>转换为【新】字节数组,指定长度。
/// </summary>
/// <typeparam name="TByteBlock">实现<see cref="IByteBlock"/>接口的字节块类型。</typeparam>
/// <param name="byteBlock">字节块对象。</param>
/// <param name="length">要转换为数组的长度。</param>
/// <returns>从当前位置开始,指定长度的【新】字节数组。</returns>
public static byte[] ToArrayTake<TByteBlock>(this TByteBlock byteBlock, long length) where TByteBlock : IBytesReader
{
return byteBlock.Sequence.Slice(0, length).ToArray();
}
public static void Write<TByteBlock>(ref TByteBlock byteBlock, ReadOnlySequence<byte> bytes) where TByteBlock : IBytesWriter
{
foreach (var item in bytes)
{
byteBlock.Write(item.Span);
}
}
public static void WriteBackValue<TWriter, T>(ref TWriter writer, T value, EndianType endianType, int pos)
where T : unmanaged
where TWriter : IByteBlockWriter
{
var nowPos = (int)writer.WrittenCount - pos;
writer.Advance(-nowPos);
var size = Unsafe.SizeOf<T>();
var span = writer.GetSpan(size);
TouchSocketBitConverter.GetBitConverter(endianType).WriteBytes(span, value);
writer.Advance(nowPos);
}
public static void WriteBackValue<TWriter, T>(ref TWriter writer, T value, EndianType endianType, long pos)
where T : unmanaged
where TWriter : IByteBlockWriter
{
var nowPos = (int)(writer.WrittenCount - pos);
writer.Advance(-nowPos);
var size = Unsafe.SizeOf<T>();
var span = writer.GetSpan(size);
TouchSocketBitConverter.GetBitConverter(endianType).WriteBytes(span, value);
writer.Advance(nowPos);
}
public static string ReadNormalString<TReader>(ref TReader reader, int length)
where TReader : IBytesReader
{
var span = reader.GetSpan(length).Slice(0, length);
var str = span.ToString(Encoding.UTF8);
reader.Advance(length);
return str;
}
public static void WriteBackNormalString<TWriter>(ref TWriter writer, string value, Encoding encoding, int pos)
where TWriter : IByteBlockWriter
{
var nowPos = (int)(writer.WrittenCount - pos);
writer.Advance(-nowPos);
WriterExtension.WriteNormalString(ref writer, value, encoding);
writer.Advance(nowPos);
}
public static int WriteNormalString(this Span<byte> span, string value, Encoding encoding)
{
var maxSize = encoding.GetMaxByteCount(value.Length);
var chars = value.AsSpan();
unsafe
{
fixed (char* p = &chars[0])
{
fixed (byte* p1 = &span[0])
{
var len = Encoding.UTF8.GetBytes(p, chars.Length, p1, maxSize);
return len;
}
}
}
}
}

View File

@@ -8,6 +8,7 @@
// QQ群605534569
//------------------------------------------------------------------------------
using System.Buffers;
using System.Text;
namespace ThingsGateway.Foundation;
@@ -47,6 +48,14 @@ public static class ByteExtensions
}
return bytes;
}
public static byte[] BoolToByte(this Span<bool> value, byte trueData = 0xff)
{
return BoolToByte((ReadOnlySpan<bool>)value, trueData); ;
}
public static byte[] BoolToByte(this bool[] value, byte trueData = 0xff)
{
return BoolToByte((ReadOnlySpan<bool>)value, trueData); ;
}
public static bool[] ByteToBool(this ReadOnlySpan<byte> value)
{
bool[] bytes = new bool[value.Length];
@@ -76,6 +85,7 @@ public static class ByteExtensions
{
return BytesAdd((ReadOnlySpan<byte>)bytes, value);
}
/// <summary>
/// 数组内容分别相加某个数字
/// </summary>
@@ -94,6 +104,31 @@ public static class ByteExtensions
return result;
}
/// <summary>
/// 将 ReadOnlySequence 的每个字节加上指定值,返回新的 byte 数组。
/// </summary>
public static byte[] BytesAdd(this ReadOnlySequence<byte> sequence, int value)
{
if (sequence.Length == 0)
return Array.Empty<byte>();
byte[] result = new byte[sequence.Length];
int offset = 0;
foreach (var segment in sequence)
{
var span = segment.Span;
for (int i = 0; i < span.Length; i++)
{
result[offset + i] = (byte)(span[i] + value);
}
offset += span.Length;
}
return result;
}
/// <summary>
/// 将byte数组按照双字节进行反转如果为单数的情况则自动补齐<br />
/// </summary>
@@ -187,6 +222,34 @@ public static class ByteExtensions
return boolArray;
}
/// <summary>
/// 从 <see cref="ReadOnlySequence{T}"/> 中提取位数组length 代表位数
/// </summary>
/// <param name="sequence">原始字节序列</param>
/// <param name="length">想要转换的位数,如果超出字节序列长度 * 8则自动缩小为最大位数</param>
/// <returns>转换后的布尔数组</returns>
public static bool[] ByteToBoolArray(this ReadOnlySequence<byte> sequence, int length)
{
// 计算字节序列能提供的最大位数
long maxBitLength = sequence.Length * 8;
if (length > maxBitLength)
length = (int)maxBitLength;
bool[] boolArray = new bool[length];
int bitIndex = 0; // 目标位索引
foreach (var segment in sequence)
{
var span = segment.Span;
for (int i = 0; i < span.Length && bitIndex < length; i++)
{
boolArray[bitIndex] = span[i / 8].BoolOnByteIndex(i % 8);
}
}
return boolArray;
}
public static ReadOnlyMemory<byte> CombineMemoryBlocks(this List<ReadOnlyMemory<byte>> blocks)
{
@@ -281,4 +344,15 @@ public static class ByteExtensions
{
return DataTransUtil.ByteToHexString(buffer, splite, newLineCount);
}
/// <summary>
/// 字节数组默认转16进制字符
/// </summary>
/// <returns></returns>
public static string ToHexString(this ReadOnlySequence<byte> buffer, char splite = default, int newLineCount = 0)
{
return DataTransUtil.ByteToHexString(buffer, splite, newLineCount);
}
}

View File

@@ -8,6 +8,8 @@
// QQ群605534569
// ------------------------------------------------------------------------------
using System.Buffers;
namespace ThingsGateway.Foundation.Extension.Generic;
/// <inheritdoc/>
@@ -245,6 +247,29 @@ public static class GenericExtensions
}
}
public static IEnumerable<ReadOnlySequence<T>> ChunkBetter<T>(this ReadOnlySequence<T> sequence, int groupSize)
{
if (groupSize <= 0)
throw new ArgumentOutOfRangeException(nameof(groupSize));
var start = sequence.Start;
var remaining = sequence.Length;
while (remaining > 0)
{
var len = (int)Math.Min(groupSize, remaining);
// 计算 chunk 的 end 位置
var end = sequence.GetPosition(len, start);
yield return sequence.Slice(start, end);
// 移动起始位置
start = end;
remaining -= len;
}
}
/// <summary>拷贝当前的实例数组,是基于引用层的浅拷贝,如果类型为值类型,那就是深度拷贝,如果类型为引用类型,就是浅拷贝</summary>
public static T[] CopyArray<T>(this T[] value)
{

View File

@@ -6,12 +6,13 @@
<PropertyGroup>
<Description>工业设备通讯协议-基础类库</Description>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Localization.Abstractions" Version="$(NET9Version)" />
<PackageReference Include="TouchSocket" Version="4.0.0-Alpha.12" />
<PackageReference Include="TouchSocket.SerialPorts" Version="4.0.0-Alpha.12" />
<PackageReference Include="TouchSocket" Version="4.0.0-beta.3" />
<PackageReference Include="TouchSocket.SerialPorts" Version="4.0.0-beta.3" />
</ItemGroup>
<ItemGroup>

View File

@@ -8,6 +8,8 @@
// QQ群605534569
//------------------------------------------------------------------------------
using System.Buffers;
namespace ThingsGateway.Foundation;
/// <summary>
@@ -70,4 +72,64 @@ public static class CRC16Utils
}
}
/// <summary>
/// 通过指定多项式码来获取对应的数据的CRC校验码
/// </summary>
/// <param name="sequence">需要校验的数据不包含CRC字节</param>
/// <returns>返回带CRC校验码的字节数组可用于串口发送</returns>
public static byte[] Crc16Only(ReadOnlySequence<byte> sequence)
{
return Crc16Only(sequence, 0xA001);
}
/// <summary>
/// 通过指定多项式码来获取对应的数据的CRC校验码
/// </summary>
/// <param name="sequence">需要校验的数据不包含CRC字节</param>
/// <param name="xdapoly">多项式</param>
/// <param name="crc16">是否低位在前true=低字节优先false=高字节优先)</param>
/// <returns>返回带CRC校验码的字节数组可用于串口发送</returns>
public static byte[] Crc16Only(ReadOnlySequence<byte> sequence, int xdapoly, bool crc16 = true)
{
int num = 0xFFFF;
foreach (var segment in sequence)
{
var span = segment.Span;
for (int i = 0; i < span.Length; i++)
{
num = (num >> (crc16 ? 0 : 8)) ^ span[i];
for (int j = 0; j < 8; j++)
{
int num2 = num & 1;
num >>= 1;
if (num2 == 1)
{
num ^= xdapoly;
}
}
}
}
if (crc16)
{
return new byte[]
{
(byte)(num & 0xFFu),
(byte)(num >> 8)
};
}
else
{
return new byte[]
{
(byte)(num >> 8),
(byte)(num & 0xFFu)
};
}
}
}

View File

@@ -8,6 +8,7 @@
// QQ群605534569
//------------------------------------------------------------------------------
using System.Buffers;
using System.Text;
namespace ThingsGateway.Foundation;
@@ -59,6 +60,53 @@ public static class DataTransUtil
return sb.ToString();
}
public static string ByteToHexString(ReadOnlySequence<byte> InBytes, char segment = default, int newLineCount = 0) => ByteToHexString(InBytes, 0, InBytes.Length, segment, newLineCount);
public static string ByteToHexString(ReadOnlySequence<byte> sequence, long offset, long length, char segment = default, int newLineCount = 0)
{
if (length <= 0 || offset < 0 || sequence.Length < offset + length)
return string.Empty;
var estimatedSize = length * (segment != default ? 3 : 2) + (length / Math.Max(newLineCount, int.MaxValue));
StringBuilder sb = new StringBuilder((int)estimatedSize);
int totalConsumed = 0;
int totalWritten = 0;
foreach (var memory in sequence)
{
var span = memory.Span;
for (int i = 0; i < span.Length && totalWritten < length; i++)
{
if (totalConsumed >= offset)
{
sb.Append(span[i].ToString("X2"));
if (totalWritten < length - 1)
{
if (segment != default)
sb.Append(segment);
if (newLineCount > 0 && (totalWritten + 1) % newLineCount == 0)
sb.AppendLine();
}
totalWritten++;
}
totalConsumed++;
if (totalConsumed >= offset + length)
break;
}
if (totalWritten >= length)
break;
}
return sb.ToString();
}
/// <summary>
/// 获取Bcd值

View File

@@ -28,7 +28,7 @@ public static class JTokenUtil
{
try
{
if (item.IsNullOrWhiteSpace())
if (item.IsNullOrEmpty())
return JValue.CreateNull();
if (bool.TryParse(item, out bool parseBool))

View File

@@ -4,6 +4,7 @@
<PropertyGroup>
<TargetFrameworks>net8.0;net9.0</TargetFrameworks>
</PropertyGroup>
<ItemGroup>

View File

@@ -4,6 +4,7 @@
<PropertyGroup>
<TargetFrameworks>net8.0;net9.0</TargetFrameworks>
<!--<UseRazorSourceGenerator>false</UseRazorSourceGenerator>-->
</PropertyGroup>

View File

@@ -18,7 +18,7 @@ namespace BootstrapBlazor.Components;
/// <param name="fieldName">字段名称</param>
/// <param name="fieldType">字段类型</param>
/// <param name="fieldText">显示文字</param>
internal sealed class InternalTableColumn(string fieldName, Type fieldType, string? fieldText = null) : IEditorItem, ITableColumn
public sealed class DefaultTableColumn(string fieldName, Type fieldType, string? fieldText = null) : IEditorItem, ITableColumn
{
public IEnumerable<KeyValuePair<string, object>>? ComponentParameters { get; set; }
public Type? ComponentType { get; set; }

View File

@@ -176,7 +176,7 @@ public class ControlController : ControllerBase, IRpcServer
[TouchSocket.WebApi.WebApi(Method = TouchSocket.WebApi.HttpMethodType.Post)]
public Task<bool> BatchSaveVariableAsync([FromBody][TouchSocket.WebApi.FromBody] List<Variable> variables, ItemChangedType type, bool restart = true)
{
return GlobalData.VariableRuntimeService.BatchSaveVariableAsync(variables, type, restart, default);
return GlobalData.VariableRuntimeService.BatchSaveVariableAsync(variables, type, restart);
}
/// <summary>
@@ -188,7 +188,7 @@ public class ControlController : ControllerBase, IRpcServer
public Task<bool> DeleteChannelAsync([FromBody][TouchSocket.WebApi.FromBody] List<long> ids, bool restart = true)
{
if (ids == null || ids.Count == 0) ids = GlobalData.IdChannels.Keys.ToList();
return GlobalData.ChannelRuntimeService.DeleteChannelAsync(ids, restart, default);
return GlobalData.ChannelRuntimeService.DeleteChannelAsync(ids, restart);
}
/// <summary>
@@ -200,7 +200,7 @@ public class ControlController : ControllerBase, IRpcServer
public Task<bool> DeleteDeviceAsync([FromBody][TouchSocket.WebApi.FromBody] List<long> ids, bool restart = true)
{
if (ids == null || ids.Count == 0) ids = GlobalData.IdDevices.Keys.ToList();
return GlobalData.DeviceRuntimeService.DeleteDeviceAsync(ids, restart, default);
return GlobalData.DeviceRuntimeService.DeleteDeviceAsync(ids, restart);
}
/// <summary>
@@ -212,7 +212,7 @@ public class ControlController : ControllerBase, IRpcServer
public Task<bool> DeleteVariableAsync([FromBody][TouchSocket.WebApi.FromBody] List<long> ids, bool restart = true)
{
if (ids == null || ids.Count == 0) ids = GlobalData.IdVariables.Keys.ToList();
return GlobalData.VariableRuntimeService.DeleteVariableAsync(ids, restart, default);
return GlobalData.VariableRuntimeService.DeleteVariableAsync(ids, restart);
}
/// <summary>
@@ -223,7 +223,7 @@ public class ControlController : ControllerBase, IRpcServer
[TouchSocket.WebApi.WebApi(Method = TouchSocket.WebApi.HttpMethodType.Post)]
public Task InsertTestDataAsync(int testVariableCount, int testDeviceCount, string slaveUrl, bool businessEnable, bool restart = true)
{
return GlobalData.VariableRuntimeService.InsertTestDataAsync(testVariableCount, testDeviceCount, slaveUrl, businessEnable, restart, default);
return GlobalData.VariableRuntimeService.InsertTestDataAsync(testVariableCount, testDeviceCount, slaveUrl, businessEnable, restart);
}
/// <summary>

View File

@@ -57,6 +57,9 @@ public abstract class BusinessBase : DriverBase
/// </summary>
protected Dictionary<string, List<VariableRuntime>> VariableRuntimeGroups { get; set; } = new();
#if !Management
public override Task AfterVariablesChangedAsync(CancellationToken cancellationToken)
{
LogMessage?.LogInformation("Refresh variable");
@@ -117,6 +120,8 @@ public abstract class BusinessBase : DriverBase
};
}
/// <summary>
/// 间隔执行
/// </summary>
@@ -134,4 +139,5 @@ public abstract class BusinessBase : DriverBase
CurrentDevice?.SetDeviceStatus(TimerX.Now, true);
}
}
#endif
}

View File

@@ -20,8 +20,27 @@ namespace ThingsGateway.Gateway.Application;
/// </summary>
public abstract class BusinessBaseWithCache : BusinessBase
{
protected sealed override BusinessPropertyBase _businessPropertyBase => _businessPropertyWithCache;
protected abstract BusinessPropertyWithCache _businessPropertyWithCache { get; }
#if !Management
protected override Task DisposeAsync(bool disposing)
{
// 清空内存队列
_memoryPluginEventDataModelQueue.Clear();
_memoryAlarmModelQueue.Clear();
_memoryDevModelQueue.Clear();
_memoryVarModelQueue.Clear();
_memoryVarModelsQueue.Clear();
return base.DisposeAsync(disposing);
}
#region
protected abstract bool PluginEventDataModelEnable { get; }
protected abstract bool AlarmModelEnable { get; }
protected abstract bool DevModelEnable { get; }
protected abstract bool VarModelEnable { get; }
@@ -30,6 +49,10 @@ public abstract class BusinessBaseWithCache : BusinessBase
if (AlarmModelEnable)
DBCacheAlarm = LocalDBCacheAlarmModel();
if (PluginEventDataModelEnable)
DBCachePluginEventData = LocalDBCachePluginEventDataModel();
if (DevModelEnable)
DBCacheDev = LocalDBCacheDevModel();
@@ -62,7 +85,10 @@ public abstract class BusinessBaseWithCache : BusinessBase
{
await UpdateAlarmModelMemory(cancellationToken).ConfigureAwait(false);
}
if (PluginEventDataModelEnable)
{
await UpdatePluginEventDataModelMemory(cancellationToken).ConfigureAwait(false);
}
if (VarModelEnable)
{
await UpdateVarModelCache(cancellationToken).ConfigureAwait(false);
@@ -77,6 +103,12 @@ public abstract class BusinessBaseWithCache : BusinessBase
if (AlarmModelEnable)
{
await UpdateAlarmModelCache(cancellationToken).ConfigureAwait(false);
}
if (PluginEventDataModelEnable)
{
await UpdatePluginEventDataModelCache(cancellationToken).ConfigureAwait(false);
}
}
#endregion
@@ -84,10 +116,12 @@ public abstract class BusinessBaseWithCache : BusinessBase
#region alarm
protected ConcurrentQueue<CacheDBItem<AlarmVariable>> _memoryAlarmModelQueue = new();
protected ConcurrentQueue<CacheDBItem<PluginEventData>> _memoryPluginEventDataModelQueue = new();
private volatile bool LocalDBCacheAlarmModelInited;
private CacheDB DBCacheAlarm;
private volatile bool LocalDBCachePluginEventDataModelInited;
private CacheDB DBCachePluginEventData;
/// <summary>
/// 入缓存
/// </summary>
@@ -136,6 +170,55 @@ public abstract class BusinessBaseWithCache : BusinessBase
}
}
/// <summary>
/// 入缓存
/// </summary>
/// <param name="data"></param>
protected virtual void AddCache(List<CacheDBItem<PluginEventData>> data)
{
if (_businessPropertyWithCache.CacheEnable && data?.Count > 0)
{
try
{
LogMessage?.LogInformation($"Add {typeof(PluginEventData).Name} data to file cache, count {data.Count}");
foreach (var item in data)
{
item.Id = CommonUtils.GetSingleId();
}
var dir = CacheDBUtil.GetCacheFilePath(CurrentDevice.Name.ToString());
var fileStart = CacheDBUtil.GetFileName($"{CurrentDevice.PluginName}_{typeof(PluginEventData).FullName}_{nameof(PluginEventData)}");
var fullName = dir.CombinePathWithOs($"{fileStart}{CacheDBUtil.EX}");
lock (cacheLock)
{
bool s = false;
while (!s)
{
s = CacheDBUtil.DeleteCache(_businessPropertyWithCache.CacheFileMaxLength, fullName);
}
using var cache = LocalDBCachePluginEventDataModel();
cache.DBProvider.Fastest<CacheDBItem<PluginEventData>>().PageSize(50000).BulkCopy(data);
}
}
catch
{
try
{
using var cache = LocalDBCachePluginEventDataModel();
lock (cache.CacheDBOption)
{
cache.DBProvider.Fastest<CacheDBItem<PluginEventData>>().PageSize(50000).BulkCopy(data);
}
}
catch (Exception ex)
{
LogMessage?.LogWarning(ex, "Add cache fail");
}
}
}
}
/// <summary>
/// 添加队列,超限后会入缓存
/// </summary>
@@ -177,6 +260,48 @@ public abstract class BusinessBaseWithCache : BusinessBase
}
}
/// <summary>
/// 添加队列,超限后会入缓存
/// </summary>
/// <param name="data"></param>
protected virtual void AddQueuePluginDataModel(CacheDBItem<PluginEventData> data)
{
if (_businessPropertyWithCache.CacheEnable)
{
//检测队列长度,超限存入缓存数据库
if (_memoryPluginEventDataModelQueue.Count > _businessPropertyWithCache.QueueMaxCount)
{
List<CacheDBItem<PluginEventData>> list = null;
lock (_memoryPluginEventDataModelQueue)
{
if (_memoryPluginEventDataModelQueue.Count > _businessPropertyWithCache.QueueMaxCount)
{
list = _memoryPluginEventDataModelQueue.ToListWithDequeue();
}
}
AddCache(list);
}
}
if (_memoryPluginEventDataModelQueue.Count > _businessPropertyWithCache.QueueMaxCount)
{
lock (_memoryPluginEventDataModelQueue)
{
if (_memoryPluginEventDataModelQueue.Count > _businessPropertyWithCache.QueueMaxCount)
{
LogMessage?.LogWarning($"{typeof(PluginEventData).Name} Queue exceeds limit, clear old data. If it doesn't work as expected, increase {_businessPropertyWithCache.QueueMaxCount} or Enable cache");
_memoryPluginEventDataModelQueue.Clear();
_memoryPluginEventDataModelQueue.Enqueue(data);
return;
}
}
}
else
{
_memoryPluginEventDataModelQueue.Enqueue(data);
}
}
/// <summary>
/// 获取缓存对象,注意每次获取的对象可能不一样,如顺序操作,需固定引用
/// </summary>
@@ -191,6 +316,20 @@ public abstract class BusinessBaseWithCache : BusinessBase
}
return cacheDb;
}
/// <summary>
/// 获取缓存对象,注意每次获取的对象可能不一样,如顺序操作,需固定引用
/// </summary>
protected virtual CacheDB LocalDBCachePluginEventDataModel()
{
var cacheDb = CacheDBUtil.GetCache(typeof(CacheDBItem<PluginEventData>), CurrentDevice.Name.ToString(), $"{CurrentDevice.PluginName}_{typeof(PluginEventData).Name}");
if (!LocalDBCachePluginEventDataModelInited)
{
cacheDb.InitDb();
LocalDBCachePluginEventDataModelInited = true;
}
return cacheDb;
}
/// <summary>
/// 需实现上传到通道
@@ -200,6 +339,16 @@ public abstract class BusinessBaseWithCache : BusinessBase
/// <returns></returns>
protected abstract ValueTask<OperResult> UpdateAlarmModel(List<CacheDBItem<AlarmVariable>> item, CancellationToken cancellationToken);
/// <summary>
/// 需实现上传到通道
/// </summary>
/// <param name="item"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
protected abstract ValueTask<OperResult> UpdatePluginEventDataModel(List<CacheDBItem<PluginEventData>> item, CancellationToken cancellationToken);
protected async Task UpdateAlarmModelCache(CancellationToken cancellationToken)
{
if (_businessPropertyWithCache.CacheEnable)
@@ -256,10 +405,69 @@ public abstract class BusinessBaseWithCache : BusinessBase
}
}
#endregion //成功上传时,补上传缓存数据
}
}
protected async Task UpdatePluginEventDataModelCache(CancellationToken cancellationToken)
{
if (_businessPropertyWithCache.CacheEnable)
{
#region //成功上传时,补上传缓存数据
if (IsConnected())
{
try
{
while (!cancellationToken.IsCancellationRequested)
{
//循环获取,固定读最大行数量,执行完成需删除行
var varList = await DBCachePluginEventData.DBProvider.Queryable<CacheDBItem<PluginEventData>>().Take(_businessPropertyWithCache.SplitSize).ToListAsync(cancellationToken).ConfigureAwait(false);
if (varList.Count != 0)
{
try
{
if (!cancellationToken.IsCancellationRequested)
{
var result = await UpdatePluginEventDataModel(varList, cancellationToken).ConfigureAwait(false);
if (result.IsSuccess)
{
//删除缓存
await DBCachePluginEventData.DBProvider.Deleteable<CacheDBItem<PluginEventData>>(varList).ExecuteCommandAsync(cancellationToken).ConfigureAwait(false);
}
else
break;
}
else
{
break;
}
}
catch (Exception ex)
{
if (success)
LogMessage?.LogWarning(ex);
success = false;
break;
}
}
else
{
break;
}
}
}
catch (Exception ex)
{
if (success)
LogMessage?.LogWarning(ex);
success = false;
}
}
#endregion //成功上传时,补上传缓存数据
}
}
protected async Task UpdateAlarmModelMemory(CancellationToken cancellationToken)
{
#region //上传设备内存队列中的数据
@@ -301,6 +509,47 @@ public abstract class BusinessBaseWithCache : BusinessBase
#endregion //上传设备内存队列中的数据
}
protected async Task UpdatePluginEventDataModelMemory(CancellationToken cancellationToken)
{
#region //上传设备内存队列中的数据
try
{
var list = _memoryPluginEventDataModelQueue.ToListWithDequeue().ChunkBetter(_businessPropertyWithCache.SplitSize);
foreach (var item in list)
{
try
{
if (!cancellationToken.IsCancellationRequested)
{
var result = await UpdatePluginEventDataModel(item, cancellationToken).ConfigureAwait(false);
if (!result.IsSuccess)
{
AddCache(item);
}
}
else
{
break;
}
}
catch (Exception ex)
{
if (success)
LogMessage?.LogWarning(ex);
success = false;
}
}
}
catch (Exception ex)
{
if (success)
LogMessage?.LogWarning(ex);
success = false;
}
#endregion //上传设备内存队列中的数据
}
#endregion
@@ -537,9 +786,7 @@ public abstract class BusinessBaseWithCache : BusinessBase
private CacheDB DBCacheVar;
private CacheDB DBCacheVars;
protected sealed override BusinessPropertyBase _businessPropertyBase => _businessPropertyWithCache;
protected abstract BusinessPropertyWithCache _businessPropertyWithCache { get; }
protected object cacheLock = new();
@@ -976,4 +1223,6 @@ public abstract class BusinessBaseWithCache : BusinessBase
}
#endregion
#endif
}

View File

@@ -19,6 +19,8 @@ namespace ThingsGateway.Gateway.Application;
/// </summary>
public abstract class BusinessBaseWithCacheAlarm : BusinessBaseWithCache
{
#if !Management
protected override bool PluginEventDataModelEnable => true;
protected override bool AlarmModelEnable => true;
protected override bool DevModelEnable => false;
@@ -56,14 +58,40 @@ public abstract class BusinessBaseWithCacheAlarm : BusinessBaseWithCache
GlobalData.AlarmChangedEvent -= AlarmValueChange;
GlobalData.ReadOnlyRealAlarmIdVariables?.ForEach(a => AlarmValueChange(a.Value));
GlobalData.AlarmChangedEvent += AlarmValueChange;
GlobalData.PluginEventHandler -= PluginEventChange;
GlobalData.PluginEventHandler += PluginEventChange;
await base.InitChannelAsync(channel, cancellationToken).ConfigureAwait(false);
}
protected override Task DisposeAsync(bool disposing)
{
GlobalData.AlarmChangedEvent -= AlarmValueChange;
GlobalData.PluginEventHandler -= PluginEventChange;
return base.DisposeAsync(disposing);
}
private void PluginEventChange(PluginEventData value)
{
if (CurrentDevice?.Pause != false)
return;
if (TaskSchedulerLoop?.Stoped == true) return;
if (!PluginEventDataModelEnable) return;
// 如果业务属性的缓存为间隔上传,则不执行后续操作
//if (_businessPropertyWithCacheInterval?.IsInterval != true)
{
PluginChange(value);
}
}
/// <summary>
/// 当报警状态变化时触发此方法。如果不需要进行报警上传,则可以忽略此方法。通常情况下,需要在此方法中执行 <see cref="BusinessBaseWithCache.AddQueuePluginDataModel(CacheDBItem{PluginEventData})"/> 方法。
/// </summary>
protected virtual void PluginChange(PluginEventData value)
{
// 在报警状态变化时执行的自定义逻辑
}
/// <summary>
/// 当报警值发生变化时触发此事件处理方法。该方法内部会检查是否需要进行报警上传,如果需要,则调用 <see cref="AlarmChange(AlarmVariable)"/> 方法。
/// </summary>
@@ -104,4 +132,6 @@ public abstract class BusinessBaseWithCacheAlarm : BusinessBaseWithCache
}
}
}
#endif
}

View File

@@ -29,6 +29,9 @@ public abstract class BusinessBaseWithCacheInterval : BusinessBaseWithCache
/// </summary>
protected abstract BusinessPropertyWithCacheInterval _businessPropertyWithCacheInterval { get; }
#if !Management
protected internal override async Task InitChannelAsync(IChannel? channel, CancellationToken cancellationToken)
{
if (AlarmModelEnable)
@@ -37,7 +40,13 @@ public abstract class BusinessBaseWithCacheInterval : BusinessBaseWithCache
GlobalData.ReadOnlyRealAlarmIdVariables?.ForEach(a => AlarmValueChange(a.Value));
GlobalData.AlarmChangedEvent += AlarmValueChange;
// 解绑全局数据的事件
}
if (PluginEventDataModelEnable)
{
GlobalData.PluginEventHandler -= PluginEventChange;
GlobalData.PluginEventHandler += PluginEventChange;
}
if (DevModelEnable)
{
@@ -61,7 +70,7 @@ public abstract class BusinessBaseWithCacheInterval : BusinessBaseWithCache
}
public override async Task AfterVariablesChangedAsync(CancellationToken cancellationToken)
{
if (AlarmModelEnable || DevModelEnable || VarModelEnable)
if (AlarmModelEnable || DevModelEnable || VarModelEnable || PluginEventDataModelEnable)
{
// 如果业务属性指定了全部变量,则设置当前设备的变量运行时列表和采集设备列表
if (_businessPropertyWithCacheInterval.IsAllVariable)
@@ -134,14 +143,12 @@ public abstract class BusinessBaseWithCacheInterval : BusinessBaseWithCache
{
// 解绑事件
GlobalData.AlarmChangedEvent -= AlarmValueChange;
GlobalData.PluginEventHandler -= PluginEventChange;
GlobalData.VariableValueChangeEvent -= VariableValueChange;
GlobalData.DeviceStatusChangeEvent -= DeviceStatusChange;
// 清空内存队列
_memoryAlarmModelQueue.Clear();
_memoryDevModelQueue.Clear();
_memoryVarModelQueue.Clear();
_memoryVarModelsQueue.Clear();
return base.DisposeAsync(disposing);
}
@@ -223,7 +230,26 @@ public abstract class BusinessBaseWithCacheInterval : BusinessBaseWithCache
{
// 在变量状态变化时执行的自定义逻辑
}
private void PluginEventChange(PluginEventData value)
{
if (CurrentDevice?.Pause != false)
return;
if (TaskSchedulerLoop?.Stoped == true) return;
if (!PluginEventDataModelEnable) return;
// 如果业务属性的缓存为间隔上传,则不执行后续操作
//if (_businessPropertyWithCacheInterval?.IsInterval != true)
{
PluginChange(value);
}
}
/// <summary>
/// 当报警状态变化时触发此方法。如果不需要进行报警上传,则可以忽略此方法。通常情况下,需要在此方法中执行 <see cref="BusinessBaseWithCache.AddQueueAlarmModel"/> 方法。
/// </summary>
protected virtual void PluginChange(PluginEventData value)
{
// 在报警状态变化时执行的自定义逻辑
}
/// <summary>
/// 当报警值发生变化时触发此事件处理方法。该方法内部会检查是否需要进行报警上传,如果需要,则调用 <see cref="AlarmChange(AlarmVariable)"/> 方法。
/// </summary>
@@ -340,5 +366,5 @@ public abstract class BusinessBaseWithCacheInterval : BusinessBaseWithCache
}
}
}
#endif
}

View File

@@ -26,6 +26,17 @@ public abstract partial class BusinessBaseWithCacheIntervalScript : BusinessBase
protected abstract BusinessPropertyWithCacheIntervalScript _businessPropertyWithCacheIntervalScript { get; }
#if !Management
protected internal override Task InitChannelAsync(IChannel? channel, CancellationToken cancellationToken)
{
CSharpScriptEngineExtension.Remove(_businessPropertyWithCacheIntervalScript.BigTextScriptAlarmModel);
CSharpScriptEngineExtension.Remove(_businessPropertyWithCacheIntervalScript.BigTextScriptDeviceModel);
CSharpScriptEngineExtension.Remove(_businessPropertyWithCacheIntervalScript.BigTextScriptPluginEventDataModel);
CSharpScriptEngineExtension.Remove(_businessPropertyWithCacheIntervalScript.BigTextScriptVariableModel);
return base.InitChannelAsync(channel, cancellationToken);
}
public virtual string[] Match(string input)
{
// 生成缓存键,以确保缓存的唯一性
@@ -62,7 +73,7 @@ public abstract partial class BusinessBaseWithCacheIntervalScript : BusinessBase
protected IEnumerable<TopicArray> GetAlarmTopicArrays(IEnumerable<AlarmVariable> item)
{
var data = Application.DynamicModelExtension.GetDynamicModel<AlarmVariable>(item, _businessPropertyWithCacheIntervalScript.BigTextScriptAlarmModel);
var data = Application.DynamicModelExtension.GetDynamicModel<AlarmVariable>(item, _businessPropertyWithCacheIntervalScript.BigTextScriptAlarmModel, LogMessage);
var topics = Match(_businessPropertyWithCacheIntervalScript.AlarmTopic);
if (topics.Length > 0)
{
@@ -86,16 +97,19 @@ public abstract partial class BusinessBaseWithCacheIntervalScript : BusinessBase
if (_businessPropertyWithCacheIntervalScript.IsAlarmList)
{
var gList = group.ToList();
var json = gList.ToSystemTextJsonUtf8Bytes(_businessPropertyWithCacheIntervalScript.JsonFormattingIndented);
// 将主题和 JSON 内容添加到列表中
yield return new(topic, json, gList.Count);
if (gList.Count > 0)
{
var json = gList.ToSystemTextJsonUtf8Bytes(_businessPropertyWithCacheIntervalScript.JsonFormattingIndented, _businessPropertyWithCacheIntervalScript.JsonIgnoreNull);
// 将主题和 JSON 内容添加到列表中
yield return new(topic, json, gList.Count);
}
}
else
{
// 如果不是报警列表,则将每个分组元素分别转换为 JSON 字符串
foreach (var gro in group)
{
var json = gro.ToSystemTextJsonUtf8Bytes(_businessPropertyWithCacheIntervalScript.JsonFormattingIndented);
var json = gro.ToSystemTextJsonUtf8Bytes(_businessPropertyWithCacheIntervalScript.JsonFormattingIndented, _businessPropertyWithCacheIntervalScript.JsonIgnoreNull);
// 将主题和 JSON 内容添加到列表中
yield return new(topic, json, 1);
}
@@ -108,23 +122,92 @@ public abstract partial class BusinessBaseWithCacheIntervalScript : BusinessBase
if (_businessPropertyWithCacheIntervalScript.IsAlarmList)
{
var gList = data.ToList();
var json = gList.ToSystemTextJsonUtf8Bytes(_businessPropertyWithCacheIntervalScript.JsonFormattingIndented);
yield return new(_businessPropertyWithCacheIntervalScript.AlarmTopic, json, gList.Count);
if (gList.Count > 0)
{
var json = gList.ToSystemTextJsonUtf8Bytes(_businessPropertyWithCacheIntervalScript.JsonFormattingIndented, _businessPropertyWithCacheIntervalScript.JsonIgnoreNull);
yield return new(_businessPropertyWithCacheIntervalScript.AlarmTopic, json, gList.Count);
}
}
else
{
foreach (var group in data)
{
var json = group.ToSystemTextJsonUtf8Bytes(_businessPropertyWithCacheIntervalScript.JsonFormattingIndented);
var json = group.ToSystemTextJsonUtf8Bytes(_businessPropertyWithCacheIntervalScript.JsonFormattingIndented, _businessPropertyWithCacheIntervalScript.JsonIgnoreNull);
yield return new(_businessPropertyWithCacheIntervalScript.AlarmTopic, json, 1);
}
}
}
}
protected IEnumerable<TopicArray> GetPluginEventDataTopicArrays(IEnumerable<PluginEventData> item)
{
var data = Application.DynamicModelExtension.GetDynamicModel<PluginEventData>(item, _businessPropertyWithCacheIntervalScript.BigTextScriptPluginEventDataModel, LogMessage);
var topics = Match(_businessPropertyWithCacheIntervalScript.PluginEventDataTopic);
if (topics.Length > 0)
{
{
//获取分组最终结果
var groups = data.GroupByKeys(topics);
foreach (var group in groups)
{
// 上传主题
// 获取预定义的报警主题
string topic = _businessPropertyWithCacheIntervalScript.PluginEventDataTopic;
// 将主题中的占位符替换为分组键对应的值
for (int i = 0; i < topics.Length; i++)
{
topic = topic.Replace(@"${" + topics[i] + @"}", group.Key[i]?.ToString());
}
// 上传内容
if (_businessPropertyWithCacheIntervalScript.IsPluginEventDataList)
{
var gList = group.ToList();
if (gList.Count > 0)
{
var json = gList.ToSystemTextJsonUtf8Bytes(_businessPropertyWithCacheIntervalScript.JsonFormattingIndented, _businessPropertyWithCacheIntervalScript.JsonIgnoreNull);
// 将主题和 JSON 内容添加到列表中
yield return new(topic, json, gList.Count);
}
}
else
{
// 如果不是报警列表,则将每个分组元素分别转换为 JSON 字符串
foreach (var gro in group)
{
var json = gro.ToSystemTextJsonUtf8Bytes(_businessPropertyWithCacheIntervalScript.JsonFormattingIndented, _businessPropertyWithCacheIntervalScript.JsonIgnoreNull);
// 将主题和 JSON 内容添加到列表中
yield return new(topic, json, 1);
}
}
}
}
}
else
{
if (_businessPropertyWithCacheIntervalScript.IsPluginEventDataList)
{
var gList = data.ToList();
if (gList.Count > 0)
{
var json = gList.ToSystemTextJsonUtf8Bytes(_businessPropertyWithCacheIntervalScript.JsonFormattingIndented, _businessPropertyWithCacheIntervalScript.JsonIgnoreNull);
yield return new(_businessPropertyWithCacheIntervalScript.PluginEventDataTopic, json, gList.Count);
}
}
else
{
foreach (var group in data)
{
var json = group.ToSystemTextJsonUtf8Bytes(_businessPropertyWithCacheIntervalScript.JsonFormattingIndented, _businessPropertyWithCacheIntervalScript.JsonIgnoreNull);
yield return new(_businessPropertyWithCacheIntervalScript.PluginEventDataTopic, json, 1);
}
}
}
}
protected IEnumerable<TopicArray> GetDeviceTopicArray(IEnumerable<DeviceBasicData> item)
{
var data = Application.DynamicModelExtension.GetDynamicModel<DeviceBasicData>(item, _businessPropertyWithCacheIntervalScript.BigTextScriptDeviceModel);
var data = Application.DynamicModelExtension.GetDynamicModel<DeviceBasicData>(item, _businessPropertyWithCacheIntervalScript.BigTextScriptDeviceModel, LogMessage);
var topics = Match(_businessPropertyWithCacheIntervalScript.DeviceTopic);
if (topics.Length > 0)
{
@@ -150,16 +233,19 @@ public abstract partial class BusinessBaseWithCacheIntervalScript : BusinessBase
{
// 如果是设备列表,则将整个分组转换为 JSON 字符串
var gList = group.Select(a => a).ToList();
var json = gList.ToSystemTextJsonUtf8Bytes(_businessPropertyWithCacheIntervalScript.JsonFormattingIndented);
// 将主题和 JSON 内容添加到列表中
yield return new(topic, json, gList.Count);
if (gList.Count > 0)
{
var json = gList.ToSystemTextJsonUtf8Bytes(_businessPropertyWithCacheIntervalScript.JsonFormattingIndented, _businessPropertyWithCacheIntervalScript.JsonIgnoreNull);
// 将主题和 JSON 内容添加到列表中
yield return new(topic, json, gList.Count);
}
}
else
{
// 如果不是设备列表,则将每个分组元素分别转换为 JSON 字符串
foreach (var gro in group)
{
var json = gro.ToSystemTextJsonUtf8Bytes(_businessPropertyWithCacheIntervalScript.JsonFormattingIndented);
var json = gro.ToSystemTextJsonUtf8Bytes(_businessPropertyWithCacheIntervalScript.JsonFormattingIndented, _businessPropertyWithCacheIntervalScript.JsonIgnoreNull);
// 将主题和 JSON 内容添加到列表中
yield return new(topic, json, 1);
}
@@ -173,14 +259,17 @@ public abstract partial class BusinessBaseWithCacheIntervalScript : BusinessBase
if (_businessPropertyWithCacheIntervalScript.IsDeviceList)
{
var gList = data.ToList();
var json = gList.ToSystemTextJsonUtf8Bytes(_businessPropertyWithCacheIntervalScript.JsonFormattingIndented);
yield return new(_businessPropertyWithCacheIntervalScript.DeviceTopic, json, gList.Count);
if (gList.Count > 0)
{
var json = gList.ToSystemTextJsonUtf8Bytes(_businessPropertyWithCacheIntervalScript.JsonFormattingIndented, _businessPropertyWithCacheIntervalScript.JsonIgnoreNull);
yield return new(_businessPropertyWithCacheIntervalScript.DeviceTopic, json, gList.Count);
}
}
else
{
foreach (var group in data)
{
var json = group.ToSystemTextJsonUtf8Bytes(_businessPropertyWithCacheIntervalScript.JsonFormattingIndented);
var json = group.ToSystemTextJsonUtf8Bytes(_businessPropertyWithCacheIntervalScript.JsonFormattingIndented, _businessPropertyWithCacheIntervalScript.JsonIgnoreNull);
yield return new(_businessPropertyWithCacheIntervalScript.DeviceTopic, json, 1);
}
}
@@ -189,7 +278,7 @@ public abstract partial class BusinessBaseWithCacheIntervalScript : BusinessBase
protected IEnumerable<TopicArray> GetVariableScriptTopicArray(IEnumerable<VariableBasicData> item)
{
var data = Application.DynamicModelExtension.GetDynamicModel<VariableBasicData>(item, _businessPropertyWithCacheIntervalScript.BigTextScriptVariableModel);
var data = Application.DynamicModelExtension.GetDynamicModel<VariableBasicData>(item, _businessPropertyWithCacheIntervalScript.BigTextScriptVariableModel, LogMessage);
var topics = Match(_businessPropertyWithCacheIntervalScript.VariableTopic);
if (topics.Length > 0)
{
@@ -215,16 +304,20 @@ public abstract partial class BusinessBaseWithCacheIntervalScript : BusinessBase
{
// 如果是变量列表,则将整个分组转换为 JSON 字符串
var gList = group.Select(a => a).ToList();
var json = gList.ToSystemTextJsonUtf8Bytes(_businessPropertyWithCacheIntervalScript.JsonFormattingIndented);
// 将主题和 JSON 内容添加到列表中
yield return new(topic, json, gList.Count);
if (gList.Count > 0)
{
var json = gList.ToSystemTextJsonUtf8Bytes(_businessPropertyWithCacheIntervalScript.JsonFormattingIndented, _businessPropertyWithCacheIntervalScript.JsonIgnoreNull);
// 将主题和 JSON 内容添加到列表中
yield return new(topic, json, gList.Count);
}
}
else
{
// 如果不是变量列表,则将每个分组元素分别转换为 JSON 字符串
foreach (var gro in group)
{
var json = gro.ToSystemTextJsonUtf8Bytes(_businessPropertyWithCacheIntervalScript.JsonFormattingIndented);
var json = gro.ToSystemTextJsonUtf8Bytes(_businessPropertyWithCacheIntervalScript.JsonFormattingIndented, _businessPropertyWithCacheIntervalScript.JsonIgnoreNull);
// 将主题和 JSON 内容添加到列表中
yield return new(topic, json, 1);
}
@@ -238,14 +331,17 @@ public abstract partial class BusinessBaseWithCacheIntervalScript : BusinessBase
if (_businessPropertyWithCacheIntervalScript.IsVariableList)
{
var gList = data.ToList();
var json = gList.ToSystemTextJsonUtf8Bytes(_businessPropertyWithCacheIntervalScript.JsonFormattingIndented);
yield return new(_businessPropertyWithCacheIntervalScript.VariableTopic, json, gList.Count);
if (gList.Count > 0)
{
var json = gList.ToSystemTextJsonUtf8Bytes(_businessPropertyWithCacheIntervalScript.JsonFormattingIndented, _businessPropertyWithCacheIntervalScript.JsonIgnoreNull);
yield return new(_businessPropertyWithCacheIntervalScript.VariableTopic, json, gList.Count);
}
}
else
{
foreach (var group in data)
{
var json = group.ToSystemTextJsonUtf8Bytes(_businessPropertyWithCacheIntervalScript.JsonFormattingIndented);
var json = group.ToSystemTextJsonUtf8Bytes(_businessPropertyWithCacheIntervalScript.JsonFormattingIndented, _businessPropertyWithCacheIntervalScript.JsonIgnoreNull);
yield return new(_businessPropertyWithCacheIntervalScript.VariableTopic, json, 1);
}
}
@@ -292,16 +388,19 @@ public abstract partial class BusinessBaseWithCacheIntervalScript : BusinessBase
{
// 如果是变量列表,则将整个分组转换为 JSON 字符串
var gList = group.Select(a => a).ToList();
var json = gList.ToSystemTextJsonUtf8Bytes(_businessPropertyWithCacheIntervalScript.JsonFormattingIndented);
// 将主题和 JSON 内容添加到列表中
yield return new(topic, json, gList.Count);
if (gList.Count > 0)
{
var json = gList.ToSystemTextJsonUtf8Bytes(_businessPropertyWithCacheIntervalScript.JsonFormattingIndented, _businessPropertyWithCacheIntervalScript.JsonIgnoreNull);
// 将主题和 JSON 内容添加到列表中
yield return new(topic, json, gList.Count);
}
}
else
{
// 如果不是变量列表,则将每个分组元素分别转换为 JSON 字符串
foreach (var gro in group)
{
var json = gro.ToSystemTextJsonUtf8Bytes(_businessPropertyWithCacheIntervalScript.JsonFormattingIndented);
var json = gro.ToSystemTextJsonUtf8Bytes(_businessPropertyWithCacheIntervalScript.JsonFormattingIndented, _businessPropertyWithCacheIntervalScript.JsonIgnoreNull);
// 将主题和 JSON 内容添加到列表中
yield return new(topic, json, 1);
}
@@ -315,14 +414,17 @@ public abstract partial class BusinessBaseWithCacheIntervalScript : BusinessBase
if (_businessPropertyWithCacheIntervalScript.IsVariableList)
{
var gList = data.ToList();
var json = gList.ToSystemTextJsonUtf8Bytes(_businessPropertyWithCacheIntervalScript.JsonFormattingIndented);
yield return new(_businessPropertyWithCacheIntervalScript.VariableTopic, json, gList.Count);
if (gList.Count > 0)
{
var json = gList.ToSystemTextJsonUtf8Bytes(_businessPropertyWithCacheIntervalScript.JsonFormattingIndented, _businessPropertyWithCacheIntervalScript.JsonIgnoreNull);
yield return new(_businessPropertyWithCacheIntervalScript.VariableTopic, json, gList.Count);
}
}
else
{
foreach (var group in data)
{
var json = group.ToSystemTextJsonUtf8Bytes(_businessPropertyWithCacheIntervalScript.JsonFormattingIndented);
var json = group.ToSystemTextJsonUtf8Bytes(_businessPropertyWithCacheIntervalScript.JsonFormattingIndented, _businessPropertyWithCacheIntervalScript.JsonIgnoreNull);
yield return new(_businessPropertyWithCacheIntervalScript.VariableTopic, json, 1);
}
}
@@ -347,4 +449,7 @@ public abstract partial class BusinessBaseWithCacheIntervalScript : BusinessBase
[GeneratedRegex(@"\$\{(.+?)\}")]
private static partial Regex TopicRegex();
#endregion
#endif
}

View File

@@ -15,9 +15,13 @@ namespace ThingsGateway.Gateway.Application;
/// </summary>
public abstract partial class BusinessBaseWithCacheIntervalScriptAll : BusinessBaseWithCacheIntervalScript
{
#if !Management
protected override bool PluginEventDataModelEnable => true;
protected override bool AlarmModelEnable => true;
protected override bool DevModelEnable => true;
protected override bool VarModelEnable => true;
#endif
}

View File

@@ -15,6 +15,8 @@ namespace ThingsGateway.Gateway.Application;
/// </summary>
public abstract class BusinessBaseWithCacheIntervalVariable : BusinessBaseWithCacheInterval
{
#if !Management
protected override bool PluginEventDataModelEnable => false;
protected override bool AlarmModelEnable => false;
protected override bool DevModelEnable => false;
@@ -29,5 +31,9 @@ public abstract class BusinessBaseWithCacheIntervalVariable : BusinessBaseWithCa
{
throw new NotImplementedException();
}
protected override ValueTask<OperResult> UpdatePluginEventDataModel(List<CacheDBItem<PluginEventData>> item, CancellationToken cancellationToken)
{
throw new NotImplementedException();
}
#endif
}

View File

@@ -28,7 +28,11 @@ public class BusinessPropertyWithCacheIntervalScript : BusinessPropertyWithCache
/// </summary>
[DynamicProperty]
public bool JsonFormattingIndented { get; set; } = true;
/// <summary>
/// 忽略Null值
/// </summary>
[DynamicProperty]
public bool JsonIgnoreNull { get; set; } = true;
/// <summary>
/// 设备Topic
/// </summary>
@@ -46,6 +50,11 @@ public class BusinessPropertyWithCacheIntervalScript : BusinessPropertyWithCache
/// </summary>
[DynamicProperty]
public bool IsAlarmList { get; set; } = true;
/// <summary>
/// 报警Topic
/// </summary>
[DynamicProperty]
public bool IsPluginEventDataList { get; set; } = true;
/// <summary>
/// 设备Topic
@@ -65,6 +74,12 @@ public class BusinessPropertyWithCacheIntervalScript : BusinessPropertyWithCache
[DynamicProperty(Remark = "可使用${key}作为匹配项key必须是上传实体中的属性比如ThingsGateway/Alarm/${DeviceName}")]
public string AlarmTopic { get; set; }
/// <summary>
/// 报警Topic
/// </summary>
[DynamicProperty(Remark = "可使用${key}作为匹配项key必须是上传实体中的属性比如ThingsGateway/PluginEventData/${DeviceName}")]
public string PluginEventDataTopic { get; set; }
/// <summary>
/// 设备实体脚本
/// </summary>
@@ -85,4 +100,11 @@ public class BusinessPropertyWithCacheIntervalScript : BusinessPropertyWithCache
[DynamicProperty]
[AutoGenerateColumn(Visible = true, IsVisibleWhenEdit = false, IsVisibleWhenAdd = false)]
public string? BigTextScriptAlarmModel { get; set; }
/// <summary>
/// 报警实体脚本
/// </summary>
[DynamicProperty]
[AutoGenerateColumn(Visible = true, IsVisibleWhenEdit = false, IsVisibleWhenAdd = false)]
public string? BigTextScriptPluginEventDataModel { get; set; }
}

View File

@@ -18,6 +18,7 @@ namespace ThingsGateway.Gateway.Application;
/// </summary>
public static class BusinessDatabaseUtil
{
/// <summary>
/// 获取数据库链接
/// </summary>
@@ -45,6 +46,7 @@ public static class BusinessDatabaseUtil
return sqlSugarClient;
}
#if !Management
/// <summary>
/// 按条件获取DB插件中的全部历史报警(不分页)
/// </summary>
@@ -144,4 +146,6 @@ public static class BusinessDatabaseUtil
return new("GetDBHistoryValues Fail", ex);
}
}
#endif
}

View File

@@ -16,7 +16,9 @@ using System.Collections.Concurrent;
using ThingsGateway.Common.Extension;
using ThingsGateway.Extension.Generic;
#if !Management
using ThingsGateway.Gateway.Application.Extensions;
#endif
using ThingsGateway.NewLife.Json.Extension;
using ThingsGateway.NewLife.Threading;
@@ -29,20 +31,31 @@ namespace ThingsGateway.Gateway.Application;
/// 采集插件继承实现不同PLC通讯
/// <para></para>
/// </summary>
public abstract partial class CollectBase : DriverBase, IRpcDriver
public abstract partial class CollectBase : DriverBase
#if !Management
, IRpcDriver
#endif
{
/// <summary>
/// 插件配置项
/// </summary>
public abstract CollectPropertyBase CollectProperties { get; }
public sealed override object DriverProperties => CollectProperties;
public virtual string GetAddressDescription()
{
return string.Empty;
}
#if !Management
/// <summary>
/// 特殊方法
/// </summary>
public List<DriverMethodInfo>? DriverMethodInfos { get; private set; }
public sealed override object DriverProperties => CollectProperties;
public override async Task AfterVariablesChangedAsync(CancellationToken cancellationToken)
{
LogMessage?.LogInformation("Refresh variable");
@@ -198,10 +211,7 @@ public abstract partial class CollectBase : DriverBase, IRpcDriver
ReadWriteLock = new(CollectProperties.DutyCycle, CollectProperties.WritePriority);
}
public virtual string GetAddressDescription()
{
return string.Empty;
}
protected virtual bool VariableSourceReadsEnable => true;
protected List<IScheduledTask> VariableTasks = new List<IScheduledTask>();
@@ -468,6 +478,7 @@ public abstract partial class CollectBase : DriverBase, IRpcDriver
}
}
/// <summary>
/// 连读打包,返回实际通讯包信息<see cref="VariableSourceRead"/>
/// <br></br>每个驱动打包方法不一样,所以需要实现这个接口
@@ -725,9 +736,11 @@ public abstract partial class CollectBase : DriverBase, IRpcDriver
protected override async Task DisposeAsync(bool disposing)
{
await base.DisposeAsync(disposing).ConfigureAwait(false);
_linkedCtsCache?.SafeDispose();
if (ReadWriteLock != null)
await ReadWriteLock.SafeDisposeAsync().ConfigureAwait(false);
await base.DisposeAsync(disposing).ConfigureAwait(false);
}
#endif
}

View File

@@ -33,13 +33,7 @@ public abstract class CollectFoundationBase : CollectBase
/// </summary>
public virtual IDevice? FoundationDevice { get; }
/// <summary>
/// 是否连接成功
/// </summary>
public override bool IsConnected()
{
return FoundationDevice?.OnLine == true;
}
public override string ToString()
{
@@ -53,6 +47,24 @@ public abstract class CollectFoundationBase : CollectBase
await FoundationDevice.SafeDisposeAsync().ConfigureAwait(false);
await base.DisposeAsync(disposing).ConfigureAwait(false);
}
public override string GetAddressDescription()
{
return FoundationDevice?.GetAddressDescription();
}
#if !Management
/// <summary>
/// 是否连接成功
/// </summary>
public override bool IsConnected()
{
return FoundationDevice?.OnLine == true;
}
/// <summary>
/// 开始通讯执行的方法
/// </summary>
@@ -66,10 +78,6 @@ public abstract class CollectFoundationBase : CollectBase
}
}
public override string GetAddressDescription()
{
return FoundationDevice?.GetAddressDescription();
}
protected override async Task TestOnline(object? state, CancellationToken cancellationToken)
{
@@ -216,4 +224,7 @@ public abstract class CollectFoundationBase : CollectBase
// 返回包含操作结果的字典
return new Dictionary<string, OperResult>(operResults);
}
#endif
}

View File

@@ -37,20 +37,6 @@ public abstract class DriverBase : AsyncDisposableObject, IDriver
#region
/// <summary>
/// 当前设备
/// </summary>
public DeviceRuntime? CurrentDevice { get; private set; }
/// <summary>
/// 当前设备Id
/// </summary>
public long DeviceId => CurrentDevice?.Id ?? 0;
/// <summary>
/// 当前设备名称
/// </summary>
public string? DeviceName => CurrentDevice?.Name;
/// <summary>
/// 调试UI Type如果不存在返回null
/// </summary>
@@ -76,25 +62,8 @@ public abstract class DriverBase : AsyncDisposableObject, IDriver
/// </summary>
public abstract object DriverProperties { get; }
/// <summary>
/// 是否执行了Start方法
/// </summary>
public bool IsStarted { get; protected set; } = false;
/// <summary>
/// 是否初始化成功,失败时不再执行,等待检测重启
/// </summary>
public bool IsInitSuccess { get; internal set; } = true;
/// <summary>
/// 是否采集插件
/// </summary>
public virtual bool? IsCollectDevice => CurrentDevice?.IsCollect;
/// <summary>
/// 暂停
/// </summary>
public bool Pause => CurrentDevice?.Pause == true;
private List<IEditorItem> pluginPropertyEditorItems;
public List<IEditorItem> PluginPropertyEditorItems
@@ -112,6 +81,82 @@ public abstract class DriverBase : AsyncDisposableObject, IDriver
private IStringLocalizer Localizer { get; }
#endregion
public virtual bool GetAuthentication(out DateTime? expireTime)
{
expireTime = null;
return true;
}
public string GetAuthString()
{
if (PluginServiceUtil.IsEducation(GetType()))
{
StringBuilder stringBuilder = new();
var ret = GetAuthentication(out var expireTime);
if (ret)
{
stringBuilder.Append(Localizer["Authorized"]);
}
else
{
stringBuilder.Append(Localizer["Unauthorized"]);
}
stringBuilder.Append(" ");
if (expireTime.HasValue && (DateTime.Now - expireTime.Value).TotalHours > -72)
{
stringBuilder.Append(',');
stringBuilder.Append(Localizer["ExpireTime", expireTime.Value.ToString("yyyy-MM-dd HH")]);
}
return stringBuilder.ToString();
}
return string.Empty;
}
#if !Management
/// <summary>
/// 是否执行了Start方法
/// </summary>
public bool IsStarted { get; protected set; } = false;
/// <summary>
/// 是否初始化成功,失败时不再执行,等待检测重启
/// </summary>
public bool IsInitSuccess { get; internal set; } = true;
/// <summary>
/// 是否采集插件
/// </summary>
public virtual bool? IsCollectDevice => CurrentDevice?.IsCollect;
/// <summary>
/// 当前设备
/// </summary>
public DeviceRuntime? CurrentDevice { get; private set; }
/// <summary>
/// 当前设备Id
/// </summary>
public long DeviceId => CurrentDevice?.Id ?? 0;
/// <summary>
/// 当前设备名称
/// </summary>
public string? DeviceName => CurrentDevice?.Name;
/// <summary>
/// 暂停
/// </summary>
public bool Pause => CurrentDevice?.Pause == true;
protected object pauseLock = new object();
/// <summary>
/// 暂停
@@ -378,38 +423,6 @@ public abstract class DriverBase : AsyncDisposableObject, IDriver
#region
public virtual bool GetAuthentication(out DateTime? expireTime)
{
expireTime = null;
return true;
}
public string GetAuthString()
{
if (PluginServiceUtil.IsEducation(GetType()))
{
StringBuilder stringBuilder = new();
var ret = GetAuthentication(out var expireTime);
if (ret)
{
stringBuilder.Append(Localizer["Authorized"]);
}
else
{
stringBuilder.Append(Localizer["Unauthorized"]);
}
stringBuilder.Append(" ");
if (expireTime.HasValue && (DateTime.Now - expireTime.Value).TotalHours > -72)
{
stringBuilder.Append(',');
stringBuilder.Append(Localizer["ExpireTime", expireTime.Value.ToString("yyyy-MM-dd HH")]);
}
return stringBuilder.ToString();
}
return string.Empty;
}
/// <summary>
/// 开始通讯执行的方法
@@ -456,4 +469,7 @@ public abstract class DriverBase : AsyncDisposableObject, IDriver
public abstract Task AfterVariablesChangedAsync(CancellationToken cancellationToken);
#endregion
#endif
}

View File

@@ -23,12 +23,16 @@ public static class DynamicModelExtension
/// <summary>
/// GetDynamicModel
/// </summary>
public static IEnumerable<object> GetDynamicModel<T>(this IEnumerable<T> datas, string script)
public static IEnumerable<object> GetDynamicModel<T>(this IEnumerable<T> datas, string script, TouchSocket.Core.ILog log)
{
if (!string.IsNullOrEmpty(script))
{
//执行脚本,获取新实体
var getDeviceModel = CSharpScriptEngineExtension.Do<IDynamicModel>(script);
if (getDeviceModel is DynamicModelBase dynamicModelBase)
{
dynamicModelBase.Logger = log;
}
return getDeviceModel.GetList(datas?.Cast<object>());
}
else
@@ -37,6 +41,7 @@ public static class DynamicModelExtension
}
}
#if !Management
/// <summary>
/// 获取变量的业务属性值
/// </summary>
@@ -49,9 +54,17 @@ public static class DynamicModelExtension
if (variableRuntime == null || propertyName.IsNullOrWhiteSpace())
return null;
// 检查是否存在对应的业务设备Id
if (variableRuntime.VariablePropertys?.TryGetValue(businessId, out var keyValuePairs) == true)
{
keyValuePairs.TryGetValue(propertyName, out var value);
return value; // 返回属性值
}
if (GlobalData.IdDevices.TryGetValue(businessId, out var deviceRuntime))
{
if (deviceRuntime.Driver?.DriverProperties is IBusinessPropertyAllVariableBase property)
if (deviceRuntime.Driver is BusinessBase businessBase && businessBase.DriverProperties is IBusinessPropertyAllVariableBase property)
{
if (property.IsAllVariable)
{
@@ -63,22 +76,18 @@ public static class DynamicModelExtension
}
else
{
return ThingsGatewayStringConverter.Default.Serialize(null, property.GetValue(propertyName, false));
return ThingsGatewayStringConverter.Default.Serialize(null, businessBase.VariablePropertys.GetValue(propertyName, false));
}
}
}
}
// 检查是否存在对应的业务设备Id
if (variableRuntime.VariablePropertys?.ContainsKey(businessId) == true)
{
variableRuntime.VariablePropertys[businessId].TryGetValue(propertyName, out var value);
return value; // 返回属性值
}
return null; // 未找到对应的业务设备Id返回null
}
#endif
public static IEnumerable<IGrouping<object[], T>> GroupByKeys<T>(this IEnumerable<T> values, params string[] keys)
{
// 获取动态对象集合中指定键的属性信息
@@ -124,3 +133,8 @@ public interface IDynamicModel
{
IEnumerable<dynamic> GetList(IEnumerable<object> datas);
}
public abstract class DynamicModelBase : IDynamicModel
{
public TouchSocket.Core.ILog Logger { get; set; }
public abstract IEnumerable<dynamic> GetList(IEnumerable<object> datas);
}

Some files were not shown because too many files have changed in this diff Show More