mirror of
				https://gitee.com/ThingsGateway/ThingsGateway.git
				synced 2025-10-31 23:53:58 +08:00 
			
		
		
		
	Compare commits
	
		
			4 Commits
		
	
	
		
			10.10.20.0
			...
			10.11.4.0
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | 2d7effadf9 | ||
|   | 346c560f8b | ||
|   | 8e3bd89f61 | ||
|   | 6da142d080 | 
| @@ -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.Contains("..") || !System.IO.File.Exists(filePath)) | ||||
|         if ((!(fileName.StartsWith(@"../Logs") || fileName.StartsWith(@"..\Logs")) && filePath.Contains("..")) || !System.IO.File.Exists(filePath)) | ||||
|         { | ||||
|             return NotFound(); | ||||
|         } | ||||
|   | ||||
| @@ -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); | ||||
|   | ||||
| @@ -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删除用户数据 | ||||
|     } | ||||
|   | ||||
| @@ -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> | ||||
|   | ||||
| @@ -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) | ||||
|   | ||||
| @@ -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> | ||||
|   | ||||
| @@ -30,11 +30,11 @@ | ||||
| 		<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.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" /> | ||||
|   | ||||
| @@ -1,11 +1,11 @@ | ||||
| <Project> | ||||
|  | ||||
| 	<PropertyGroup> | ||||
| 		<PluginVersion>10.10.20</PluginVersion> | ||||
| 		<ProPluginVersion>10.10.20</ProPluginVersion> | ||||
| 		<DefaultVersion>10.10.20</DefaultVersion> | ||||
| 		<AuthenticationVersion>10.10.1</AuthenticationVersion> | ||||
| 		<SourceGeneratorVersion>10.10.1</SourceGeneratorVersion> | ||||
| 		<PluginVersion>10.11.4</PluginVersion> | ||||
| 		<ProPluginVersion>10.11.4</ProPluginVersion> | ||||
| 		<DefaultVersion>10.11.4</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> | ||||
|   | ||||
							
								
								
									
										3088
									
								
								src/Drivers/ThingsGateway.AllenBradley.deps.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3088
									
								
								src/Drivers/ThingsGateway.AllenBradley.deps.json
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								src/Drivers/ThingsGateway.AllenBradley.dll
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								src/Drivers/ThingsGateway.AllenBradley.dll
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| @@ -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); | ||||
|   | ||||
| @@ -8,7 +8,7 @@ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
| 
 | ||||
| namespace ThingsGateway.Gateway.Razor; | ||||
| namespace ThingsGateway.Debug; | ||||
| 
 | ||||
| public class ValueTransformConfig | ||||
| { | ||||
| @@ -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"> | ||||
| @@ -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 修改 | ||||
| } | ||||
| @@ -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", | ||||
|   | ||||
| @@ -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", | ||||
|   | ||||
| @@ -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(); | ||||
|     } | ||||
| } | ||||
| @@ -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); | ||||
|   | ||||
| @@ -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); | ||||
|   | ||||
| @@ -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; | ||||
|   | ||||
| @@ -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> | ||||
|   | ||||
| @@ -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); | ||||
| } | ||||
|   | ||||
| @@ -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($"当前适配器为空或者不支持对象发送。"); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -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);//如果本插件无法处理当前数据,请将数据转至下一个插件。 | ||||
|     } | ||||
|  | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -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; | ||||
|   | ||||
| @@ -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"); | ||||
|         }); | ||||
|             }; | ||||
|         } | ||||
|   | ||||
| @@ -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); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -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); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -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; | ||||
|  | ||||
|   | ||||
| @@ -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; | ||||
|  | ||||
|   | ||||
| @@ -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); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -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); | ||||
|         } | ||||
|  | ||||
|     } | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -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 | ||||
|         { | ||||
|   | ||||
| @@ -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 | ||||
| { | ||||
|  | ||||
| } | ||||
| @@ -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; } | ||||
|     } | ||||
| } | ||||
| @@ -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); | ||||
| } | ||||
|   | ||||
| @@ -13,6 +13,6 @@ namespace ThingsGateway.Foundation; | ||||
| /// <summary> | ||||
| /// 发送消息 | ||||
| /// </summary> | ||||
| public interface ISendMessage : IRequestInfo, IWaitHandle, IRequestInfoByteBlockWriterBuilder | ||||
| public interface ISendMessage : IRequestInfo, IWaitHandle, IRequestInfoBuilder | ||||
| { | ||||
| } | ||||
|   | ||||
| @@ -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) | ||||
|     { | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -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; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -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); | ||||
|                     } | ||||
|                 } | ||||
|   | ||||
| @@ -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); | ||||
|                 } | ||||
|         } | ||||
|     } | ||||
|   | ||||
| @@ -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 : IBytesWriter | ||||
|     { | ||||
|         if (writer.SupportsRewind) | ||||
|         { | ||||
|             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); | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             throw new InvalidOperationException("Writer version mismatch or does not support rewind."); | ||||
|         } | ||||
|     } | ||||
|     public static void WriteBackValue<TWriter, T>(ref TWriter writer, T value, EndianType endianType, long pos) | ||||
| where T : unmanaged | ||||
| where TWriter : IBytesWriter | ||||
|     { | ||||
|         if (writer.SupportsRewind) | ||||
|         { | ||||
|             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); | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             throw new InvalidOperationException("Writer version mismatch or does not support rewind."); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     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 : IBytesWriter | ||||
|     { | ||||
|         if (writer.SupportsRewind) | ||||
|         { | ||||
|             var nowPos = (int)(writer.WrittenCount - pos); | ||||
|             writer.Advance(-nowPos); | ||||
|             WriterExtension.WriteNormalString(ref writer, value, encoding); | ||||
|             writer.Advance(nowPos); | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             throw new InvalidOperationException("Writer version mismatch or does not support rewind."); | ||||
|         } | ||||
|  | ||||
|     } | ||||
| } | ||||
| @@ -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); | ||||
|     } | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -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) | ||||
|     { | ||||
|   | ||||
| @@ -11,8 +11,8 @@ | ||||
|  | ||||
| 	<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.1" /> | ||||
| 		<PackageReference Include="TouchSocket.SerialPorts" Version="4.0.0-beta.1" /> | ||||
| 	</ItemGroup> | ||||
|  | ||||
| 	<ItemGroup> | ||||
|   | ||||
| @@ -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) | ||||
|             }; | ||||
|         } | ||||
|     } | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -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值 | ||||
|   | ||||
| @@ -234,7 +234,9 @@ public partial class VariableRuntime : Variable | ||||
|     private DateTime collectTime = DateTime.UnixEpoch.ToLocalTime(); | ||||
|  | ||||
| #pragma warning disable CS0414 | ||||
| #pragma warning disable CS0169 | ||||
|     private bool _isOnlineChanged; | ||||
| #pragma warning restore CS0169 | ||||
| #pragma warning restore CS0414 | ||||
|     private bool _valueInited; | ||||
|  | ||||
|   | ||||
| @@ -134,7 +134,7 @@ public partial class ManagementTask : AsyncDisposableObject | ||||
|                    { | ||||
|                        try | ||||
|                        { | ||||
|                            await tcpDmtpClient.ResetIdAsync($"{_managementOptions.Name}:{GlobalData.HardwareJob.HardwareInfo.UUID}").ConfigureAwait(false); | ||||
|                            await tcpDmtpClient.ResetIdAsync($"{_managementOptions.Name}:{GlobalData.HardwareJob.HardwareInfo.UUID}", tcpDmtpClient.ClosedToken).ConfigureAwait(false); | ||||
|                        } | ||||
|                        catch (Exception) | ||||
|                        { | ||||
|   | ||||
| @@ -27,7 +27,7 @@ internal sealed class RedundancyHostedService : BackgroundService, IRedundancyHo | ||||
|     protected override async Task ExecuteAsync(CancellationToken stoppingToken) | ||||
|     { | ||||
|         await Task.Yield(); | ||||
|         await RedundancyTask.StartRedundancyTaskAsync(stoppingToken).ConfigureAwait(false); | ||||
|         await RedundancyTask.StartRedundancyTaskAsync().ConfigureAwait(false); | ||||
|     } | ||||
|  | ||||
|     public Task StartRedundancyTaskAsync() => RedundancyTask.StartRedundancyTaskAsync(); | ||||
|   | ||||
| @@ -21,7 +21,7 @@ using TouchSocket.Core; | ||||
| using TouchSocket.Dmtp; | ||||
| using TouchSocket.Dmtp.Rpc; | ||||
| using TouchSocket.Rpc; | ||||
| using TouchSocket.Rpc.Generators; | ||||
| using TouchSocket.Rpc.DmtpRpc.Generators; | ||||
| using TouchSocket.Sockets; | ||||
|  | ||||
| namespace ThingsGateway.Gateway.Application; | ||||
| @@ -140,8 +140,8 @@ internal sealed class RedundancyTask : IRpcDriver, IAsyncDisposable | ||||
|                 { | ||||
|                     try | ||||
|                     { | ||||
|                         // 发送 Ping 请求以检查设备是否在线,超时时间为 10000 毫秒 | ||||
|                         online = await _tcpDmtpClient.PingAsync(10000).ConfigureAwait(false); | ||||
|                         using var cts = new CancellationTokenSource(10000); | ||||
|                         online = (await _tcpDmtpClient.PingAsync(cts.Token).ConfigureAwait(false)).IsSuccess; | ||||
|                         if (online) | ||||
|                             break; | ||||
|                         else | ||||
| @@ -238,7 +238,7 @@ internal sealed class RedundancyTask : IRpcDriver, IAsyncDisposable | ||||
|         return GlobalData.ChannelRuntimeService.RestartChannelAsync(GlobalData.ReadOnlyIdChannels.Values); | ||||
|     } | ||||
|  | ||||
|     public async Task StartRedundancyTaskAsync(CancellationToken cancellationToken = default) | ||||
|     public async Task StartRedundancyTaskAsync() | ||||
|     { | ||||
|         await StopRedundancyTaskAsync().ConfigureAwait(false); | ||||
|         RedundancyOptions = (await _redundancyService.GetRedundancyAsync().ConfigureAwait(false)).AdaptRedundancyOptions(); | ||||
| @@ -268,11 +268,11 @@ internal sealed class RedundancyTask : IRpcDriver, IAsyncDisposable | ||||
|             LogMessage?.LogInformation($"Redundancy task started"); | ||||
|             if (RedundancyOptions.IsMaster) | ||||
|             { | ||||
|                 scheduledTask = new ScheduledAsyncTask(RedundancyOptions.SyncInterval, DoMasterWork, null, null, cancellationToken); | ||||
|                 scheduledTask = new ScheduledAsyncTask(RedundancyOptions.SyncInterval, DoMasterWork, null, null, CancellationToken.None); | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 scheduledTask = new ScheduledAsyncTask(5000, DoSlaveWork, null, null, cancellationToken); | ||||
|                 scheduledTask = new ScheduledAsyncTask(5000, DoSlaveWork, null, null, CancellationToken.None); | ||||
|             } | ||||
|  | ||||
|             scheduledTask.Start(); | ||||
| @@ -416,7 +416,6 @@ internal sealed class RedundancyTask : IRpcDriver, IAsyncDisposable | ||||
|         { | ||||
|             FeedbackType = FeedbackType.WaitInvoke, | ||||
|             Token = cancellationToken, | ||||
|             Timeout = 1800000, | ||||
|             SerializationType = SerializationType.Json, | ||||
|         }; | ||||
|     } | ||||
| @@ -591,11 +590,11 @@ internal sealed class RedundancyTask : IRpcDriver, IAsyncDisposable | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private async Task<Dictionary<string, Dictionary<string, OperResult<object>>>> InvokeRpcClientAsync( | ||||
|     private Task<Dictionary<string, Dictionary<string, OperResult<object>>>> InvokeRpcClientAsync( | ||||
|         Dictionary<string, Dictionary<string, string>> deviceDatas, | ||||
|         DmtpInvokeOption invokeOption) | ||||
|     { | ||||
|         return await _tcpDmtpClient.GetDmtpRpcActor().RpcAsync(deviceDatas, invokeOption).ConfigureAwait(false); | ||||
|         return _tcpDmtpClient.GetDmtpRpcActor().RpcAsync(deviceDatas, invokeOption); | ||||
|     } | ||||
|     private async Task InvokeRpcServerAsync( | ||||
|         Dictionary<string, Dictionary<string, string>> deviceDatas, | ||||
|   | ||||
| @@ -39,7 +39,7 @@ public class ExecuteScriptNode : TextNode, IActuatorNode, IExexcuteExpressionsBa | ||||
|                     TopicArray topicArray = new() | ||||
|                     { | ||||
|                         Topic = "test", | ||||
|                         Json = Encoding.UTF8.GetBytes("test") | ||||
|                         Payload = Encoding.UTF8.GetBytes("test") | ||||
|                     }; | ||||
|                     var result = await mqttClient.MqttUpAsync(topicArray, default).ConfigureAwait(false);// 主题 和 负载 | ||||
|                     if (!result.IsSuccess) | ||||
| @@ -56,7 +56,7 @@ public class ExecuteScriptNode : TextNode, IActuatorNode, IExexcuteExpressionsBa | ||||
|                     //TopicArray topicArray = new() | ||||
|                     //{ | ||||
|                     //    Topic = "test", | ||||
|                     //    Json = Encoding.UTF8.GetBytes("test") | ||||
|                     //    Payload = Encoding.UTF8.GetBytes("test") | ||||
|                     //}; | ||||
|                     //var result = await mqttClient.MqttUpAsync(topicArray, default).ConfigureAwait(false);// 主题 和 负载 | ||||
|                     //if (!result.IsSuccess) | ||||
|   | ||||
| @@ -545,7 +545,7 @@ internal sealed class VariableService : BaseService<Variable>, IVariableService | ||||
|  | ||||
|     /// <inheritdoc/> | ||||
|     [OperDesc("ImportVariable", isRecordPar: false, localizerType: typeof(Variable))] | ||||
|     public async Task<HashSet<long>> ImportVariableAsync(Dictionary<string, ImportPreviewOutputBase> input) | ||||
|     public Task<HashSet<long>> ImportVariableAsync(Dictionary<string, ImportPreviewOutputBase> input) | ||||
|     { | ||||
|         IEnumerable<Variable>? variables = new List<Variable>(); | ||||
|         foreach (var item in input) | ||||
| @@ -559,7 +559,7 @@ internal sealed class VariableService : BaseService<Variable>, IVariableService | ||||
|         } | ||||
|         var upData = variables.Where(a => a.IsUp).ToList(); | ||||
|         var insertData = variables.Where(a => !a.IsUp).ToList(); | ||||
|         return await ImportVariableAsync(upData, insertData).ConfigureAwait(false); | ||||
|         return ImportVariableAsync(upData, insertData); | ||||
|     } | ||||
|  | ||||
|     [OperDesc("ImportVariable", isRecordPar: false, localizerType: typeof(Variable))] | ||||
|   | ||||
| @@ -11,8 +11,8 @@ | ||||
| 		<PackageReference Include="Riok.Mapperly" Version="4.2.1" ExcludeAssets="runtime" PrivateAssets="all" /> | ||||
| 		<PackageReference Include="Rougamo.Fody" Version="5.0.1" /> | ||||
| 		<PackageReference Include="System.Linq.Async" Version="6.0.3" /> | ||||
| 		<PackageReference Include="TouchSocket.Dmtp" Version="4.0.0-Alpha.12" /> | ||||
| 		<PackageReference Include="TouchSocket.WebApi.Swagger" Version="4.0.0-Alpha.12" /> | ||||
| 		<PackageReference Include="TouchSocket.Dmtp" Version="4.0.0-beta.1" /> | ||||
| 		<PackageReference Include="TouchSocket.WebApi.Swagger" Version="4.0.0-beta.1" /> | ||||
| 		<PackageReference Include="ThingsGateway.Authentication" Version="$(AuthenticationVersion)" /> | ||||
| 		<!--<ProjectReference Include="..\..\PluginPro\ThingsGateway.Authentication\ThingsGateway.Authentication.csproj" />--> | ||||
|  | ||||
|   | ||||
| @@ -34,22 +34,6 @@ | ||||
|     "SaveUpdateZipFile": "Upload Version Package" | ||||
|   }, | ||||
|  | ||||
|   "ThingsGateway.Gateway.Razor.ValueTransformType": { | ||||
|     "None": "None", | ||||
|     "Linear": "Linear", | ||||
|     "Sqrt": "Sqrt" | ||||
|   }, | ||||
|  | ||||
|   "ThingsGateway.Gateway.Razor.ValueTransformConfig": { | ||||
|     "TransformType": "TransformType", | ||||
|     "MinMax": "MinMax", | ||||
|     "ClampToRawRange": "ClampToRawRange", | ||||
|     "DecimalPlaces": "DecimalPlaces", | ||||
|     "RawMin": "RawMin", | ||||
|     "RawMax": "RawMax", | ||||
|     "ActualMin": "ActualMin", | ||||
|     "ActualMax": "ActualMax" | ||||
|   }, | ||||
|  | ||||
|  | ||||
|   "ThingsGateway.Gateway.Razor.Authentication": { | ||||
|   | ||||
| @@ -34,21 +34,7 @@ | ||||
|     "SaveUpdateZipFile": "上传版本包" | ||||
|   }, | ||||
|  | ||||
|   "ThingsGateway.Gateway.Razor.ValueTransformType": { | ||||
|     "None": "无", | ||||
|     "Linear": "线性", | ||||
|     "Sqrt": "开方" | ||||
|   }, | ||||
|   "ThingsGateway.Gateway.Razor.ValueTransformConfig": { | ||||
|     "TransformType": "转换方式", | ||||
|     "MinMax": "最小最大值", | ||||
|     "ClampToRawRange": "限制范围", | ||||
|     "DecimalPlaces": "保留小数位", | ||||
|     "RawMin": "原始最小值", | ||||
|     "RawMax": "原始最大值", | ||||
|     "ActualMin": "实际最小值", | ||||
|     "ActualMax": "实际最大值" | ||||
|   }, | ||||
|  | ||||
|  | ||||
|   "ThingsGateway.Gateway.Razor.Authentication": { | ||||
|     "AuthName": "公司名称", | ||||
|   | ||||
| @@ -13,6 +13,7 @@ using Microsoft.AspNetCore.Components.Web; | ||||
|  | ||||
| using System.Collections.Concurrent; | ||||
|  | ||||
| using ThingsGateway.Debug; | ||||
| using ThingsGateway.NewLife.Extension; | ||||
| using ThingsGateway.NewLife.Json.Extension; | ||||
|  | ||||
|   | ||||
| @@ -11,6 +11,10 @@ | ||||
|  | ||||
| using Newtonsoft.Json.Linq; | ||||
|  | ||||
| using System.Text; | ||||
|  | ||||
| using TouchSocket.Core; | ||||
|  | ||||
| using Size = BootstrapBlazor.Components.Size; | ||||
|  | ||||
| namespace ThingsGateway.Gateway.Razor | ||||
| @@ -39,18 +43,30 @@ namespace ThingsGateway.Gateway.Razor | ||||
|     { | ||||
|         {nameof(ScriptEdit.OnCheckScript),  new Func<string,Task<string>>(async a=>{ | ||||
|             if(Node is IConditionNode conditionNode) | ||||
|           return  (await conditionNode.ExecuteAsync(new NodeInput(){Value=a==null?a:JToken.Parse(a??string.Empty) },default).ConfigureAwait(false)).ToString(); | ||||
|             { | ||||
|                 StringBuilder stringBuilder=new(); | ||||
|                 conditionNode.Logger=new EasyLogger((a)=>stringBuilder.AppendLine(a)); | ||||
|                 var out1=  (await conditionNode.ExecuteAsync(new NodeInput(){Value=a==null?a:JToken.Parse(a??string.Empty) },default).ConfigureAwait(false)).ToString(); | ||||
|                 stringBuilder.AppendLine(out1); | ||||
|                 return stringBuilder.ToString(); | ||||
|             } | ||||
|              if(Node is IExpressionNode expressionNode) | ||||
|             { | ||||
|                 StringBuilder stringBuilder=new(); | ||||
|                 expressionNode.Logger=new EasyLogger((a)=>stringBuilder.AppendLine(a)); | ||||
|                 var data=await expressionNode.ExecuteAsync(new NodeInput(){Value=a==null?a:JToken.Parse(a??string.Empty) },default).ConfigureAwait(false); | ||||
|  | ||||
|                return  data.IsSuccess? data.Content.JToken?.ToString()??string.Empty: data.ToString(); | ||||
|                 stringBuilder.AppendLine( data.IsSuccess? data.Content.JToken?.ToString()??string.Empty: data.ToString()); | ||||
|                 return stringBuilder.ToString(); | ||||
|             } | ||||
|              if(Node is IActuatorNode actuatorNode) | ||||
|             { | ||||
|                                 var data=await actuatorNode.ExecuteAsync(new NodeInput(){Value=a==null?a:JToken.Parse(a??string.Empty) },default).ConfigureAwait(false); | ||||
|                 StringBuilder stringBuilder=new(); | ||||
|                 actuatorNode.Logger=new EasyLogger((a)=>stringBuilder.AppendLine(a)); | ||||
|                 var data=await actuatorNode.ExecuteAsync(new NodeInput(){Value=a==null?a:JToken.Parse(a??string.Empty) },default).ConfigureAwait(false); | ||||
|  | ||||
|                return  data.IsSuccess? data.Content.JToken?.ToString()??string.Empty: data.ToString(); | ||||
|                 stringBuilder.AppendLine( data.IsSuccess? data.Content.JToken?.ToString()??string.Empty: data.ToString()); | ||||
|                 return stringBuilder.ToString(); | ||||
|             } | ||||
|         return string.Empty; | ||||
|         }) }, | ||||
|   | ||||
| @@ -12,7 +12,7 @@ | ||||
| 		<ProjectReference Include="..\ThingsGateway.Gateway.Application\ThingsGateway.Gateway.Application.csproj" /> | ||||
| 		<PackageReference Include="Riok.Mapperly" Version="4.2.1" ExcludeAssets="runtime" PrivateAssets="all" /> | ||||
| 		<PackageReference Include="BootstrapBlazor.WinBox" Version="9.0.7" /> | ||||
| 		<PackageReference Include="BootstrapBlazor.CodeEditor" Version="9.0.1" /> | ||||
| 		<PackageReference Include="BootstrapBlazor.CodeEditor" Version="9.0.3" /> | ||||
| 		<ProjectReference Include="..\..\Admin\ThingsGateway.Admin.Razor\ThingsGateway.Admin.Razor.csproj" /> | ||||
| 		<ProjectReference Include="..\..\Foundation\ThingsGateway.Foundation.Razor\ThingsGateway.Foundation.Razor.csproj" /> | ||||
| 	</ItemGroup> | ||||
|   | ||||
| @@ -8,6 +8,8 @@ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
| using System.Buffers; | ||||
|  | ||||
| using TouchSocket.Core; | ||||
|  | ||||
| namespace ThingsGateway.Foundation.Dlt645; | ||||
| @@ -28,41 +30,62 @@ public class Dlt645_2007Response : Dlt645_2007Request | ||||
| /// </summary> | ||||
| public class Dlt645_2007Message : MessageBase, IResultMessage | ||||
| { | ||||
|     private readonly byte[] ReadStation = [0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA]; | ||||
|     private static readonly byte[] ReadStation = [0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA]; | ||||
|  | ||||
|     private int HeadCodeIndex; | ||||
|     private long HeadCodeIndex; | ||||
|  | ||||
|     public Dlt645_2007Send? Dlt645_2007Send { get; set; } | ||||
|  | ||||
|     /// <inheritdoc/> | ||||
|     public override int HeaderLength { get; set; } = 10; | ||||
|     public override long HeaderLength { get; set; } = 10; | ||||
|  | ||||
|     public Dlt645_2007Address? Request { get; set; } | ||||
|  | ||||
|     /// <inheritdoc/> | ||||
|     public override bool CheckHead<TByteBlock>(ref TByteBlock byteBlock) | ||||
|     { | ||||
|         //因为设备可能带有FE前导符开头,这里找到0x68的位置 | ||||
|         if (byteBlock != null) | ||||
|         { | ||||
|             var index = byteBlock.TotalSequence.IndexOf(0x68); | ||||
|             if (index > -1) | ||||
|             { | ||||
|                 HeadCodeIndex = index; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         //帧起始符 地址域  帧起始符 控制码 数据域长度共10个字节 | ||||
|         HeaderLength = HeadCodeIndex - byteBlock.BytesRead + 10; | ||||
|         if (byteBlock.BytesRead + byteBlock.BytesRemaining > HeadCodeIndex + 9) | ||||
|         { | ||||
|             BodyLength = byteBlock.TotalSequence.GetByte(HeadCodeIndex + 9) + 2; | ||||
|         } | ||||
|         return true; | ||||
|  | ||||
|     } | ||||
|  | ||||
|     public override FilterResult CheckBody<TByteBlock>(ref TByteBlock byteBlock) | ||||
|     { | ||||
|         var span = byteBlock.Span; | ||||
|         var pos = byteBlock.Position - HeaderLength; | ||||
|         var sequence = byteBlock.TotalSequence; | ||||
|         var pos = byteBlock.BytesRead - HeaderLength; | ||||
|         var endIndex = HeaderLength + BodyLength + pos; | ||||
|         if (span[endIndex - 1] == 0x16) | ||||
|  | ||||
|         if (sequence.GetByte(endIndex - 1) == 0x16) | ||||
|         { | ||||
|             //检查校验码 | ||||
|             int sumCheck = 0; | ||||
|             for (int i = HeadCodeIndex; i < endIndex - 2; i++) | ||||
|                 sumCheck += span[i]; | ||||
|             if ((byte)sumCheck != span[endIndex - 2]) | ||||
|             var sumCheck = sequence.SumRange(HeadCodeIndex, endIndex - HeadCodeIndex - 2); | ||||
|             if ((byte)sumCheck != sequence.GetByte(endIndex - 2)) | ||||
|             { | ||||
|                 //校验错误 | ||||
|                 ErrorMessage = AppResource.SumError; | ||||
|                 OperCode = 999; | ||||
|                 return FilterResult.Success; | ||||
|             } | ||||
|             var Station = byteBlock.Span.Slice(HeadCodeIndex + 1, 6); | ||||
|             byte? ErrorCode; | ||||
|             var controlCode = span[HeadCodeIndex + 8]; | ||||
|             var controlCode = sequence.GetByte(HeadCodeIndex + 8); | ||||
|             if ((controlCode & 0x40) == 0x40)//控制码bit6为1时,返回错误 | ||||
|             { | ||||
|                 ErrorCode = (byte)(span[HeadCodeIndex + 10] - 0x33); | ||||
|                 ErrorCode = (byte)(sequence.GetByte(HeadCodeIndex + 10) - 0x33); | ||||
|                 var error = Dlt645Helper.Get2007ErrorMessage(ErrorCode.Value); | ||||
|                 ErrorMessage = string.Format(AppResource.FunctionError, $"0x{controlCode:X2}", error); | ||||
|                 OperCode = 999; | ||||
| @@ -71,6 +94,7 @@ public class Dlt645_2007Message : MessageBase, IResultMessage | ||||
|  | ||||
|             if (Dlt645_2007Send != null) | ||||
|             { | ||||
|                 var Station = sequence.Slice(HeadCodeIndex + 1, 6); | ||||
|                 if (!Station.SequenceEqual(Request.Station.Span))//设备地址不符合时,返回错误 | ||||
|                 { | ||||
|                     if (!Request.Station.Span.SequenceEqual(ReadStation))//读写通讯地址例外 | ||||
| @@ -90,7 +114,7 @@ public class Dlt645_2007Message : MessageBase, IResultMessage | ||||
|                 if (Dlt645_2007Send.ControlCode == ControlCode.Read || Dlt645_2007Send.ControlCode == ControlCode.Write) | ||||
|                 { | ||||
|                     //数据标识不符合时,返回错误 | ||||
|                     var DataId = byteBlock.Span.Slice(HeadCodeIndex + 10, 4).BytesAdd(-0x33).AsSpan(); | ||||
|                     var DataId = sequence.Slice(HeadCodeIndex + 10, 4).BytesAdd(-0x33).AsSpan(); | ||||
|                     if (!DataId.SequenceEqual(Request.DataId.Span)) | ||||
|                     { | ||||
|                         ErrorMessage = AppResource.DataIdNotSame; | ||||
| @@ -101,38 +125,15 @@ public class Dlt645_2007Message : MessageBase, IResultMessage | ||||
|             } | ||||
|  | ||||
|             OperCode = 0; | ||||
|             Content = byteBlock.ToArray(HeadCodeIndex + 10, BodyLength - 2); | ||||
|             Content = byteBlock.TotalSequence.Slice(HeadCodeIndex + 10, BodyLength - 2).ToArray(); | ||||
|             return FilterResult.Success; | ||||
|         } | ||||
|  | ||||
|         return FilterResult.GoOn; | ||||
|     } | ||||
|  | ||||
|     /// <inheritdoc/> | ||||
|     public override bool CheckHead<TByteBlock>(ref TByteBlock byteBlock) | ||||
|     { | ||||
|         //因为设备可能带有FE前导符开头,这里找到0x68的位置 | ||||
|         var span = byteBlock.Span; | ||||
|         if (byteBlock != null) | ||||
|         { | ||||
|             for (int index = byteBlock.Position; index < byteBlock.Length; index++) | ||||
|             { | ||||
|                 if (span[index] == 0x68) | ||||
|                 { | ||||
|                     HeadCodeIndex = index; | ||||
|                     break; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         //帧起始符 地址域  帧起始符 控制码 数据域长度共10个字节 | ||||
|         HeaderLength = HeadCodeIndex - byteBlock.Position + 10; | ||||
|         if (byteBlock.Length > HeadCodeIndex + 9) | ||||
|             BodyLength = span[HeadCodeIndex + 9] + 2; | ||||
|  | ||||
|         return true; | ||||
|     } | ||||
|     public override void SendInfo(ISendMessage sendMessage, ref ValueByteBlock byteBlock) | ||||
|     public override void SendInfo(ISendMessage sendMessage) | ||||
|     { | ||||
|         Dlt645_2007Send = ((Dlt645_2007Send)sendMessage); | ||||
|         Request = Dlt645_2007Send.Dlt645_2007Address; | ||||
|   | ||||
| @@ -113,7 +113,7 @@ public class Dlt645_2007Send : ISendMessage | ||||
|     public int Sign { get; set; } | ||||
|     internal Dlt645_2007Address Dlt645_2007Address { get; } | ||||
|  | ||||
|     public void Build<TByteBlock>(ref TByteBlock byteBlock) where TByteBlock : IByteBlockWriter | ||||
|     public void Build<TByteBlock>(ref TByteBlock byteBlock) where TByteBlock : IBytesWriter | ||||
|     { | ||||
|         if (Dlt645_2007Address?.DataId.Length < 4) | ||||
|         { | ||||
| @@ -124,16 +124,17 @@ public class Dlt645_2007Send : ISendMessage | ||||
|             byteBlock.Write(Fehead.Span);//帧起始符 | ||||
|             SendHeadCodeIndex = Fehead.Length; | ||||
|         } | ||||
|         var span = byteBlock.GetSpan(256); | ||||
|  | ||||
|         WriterExtension.WriteValue(ref byteBlock, (byte)0x68);//帧起始符 | ||||
|         byteBlock.Write(Dlt645_2007Address.Station.Span);//6个字节地址域 | ||||
|         WriterExtension.WriteValue(ref byteBlock, (byte)0x68);//帧起始符 | ||||
|         WriterExtension.WriteValue(ref byteBlock, (byte)ControlCode);//控制码 | ||||
|  | ||||
|         WriterExtension.WriteValue(ref byteBlock, (byte)(Dlt645_2007Address.DataId.Length));//数据域长度 | ||||
|         byteBlock.Write(Dlt645_2007Address.DataId.Span);//数据域标识DI3、DI2、DI1、DI0 | ||||
|         var writerLenAnchor = new WriterAnchor<TByteBlock>(ref byteBlock, 1); | ||||
|         byteBlock.Write(Dlt645_2007Address.DataId.Span.BytesAdd(0x33));//数据域标识DI3、DI2、DI1、DI0 | ||||
|  | ||||
|         byteBlock.Write(Codes.Span); | ||||
|         byteBlock.Write(Codes.Span.BytesAdd(0x33)); | ||||
|  | ||||
|         if (Datas.Length > 0) | ||||
|         { | ||||
| @@ -180,18 +181,15 @@ public class Dlt645_2007Send : ISendMessage | ||||
|                     } | ||||
|                 } | ||||
|  | ||||
|                 byteBlock.Write(data); | ||||
|                 byteBlock.Write(data.BytesAdd(0x33)); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         ByteBlockExtension.WriteBackValue(ref byteBlock, (byte)(byteBlock.Length - 10 - Fehead.Length), Fehead.Length + 9);//数据域长度 | ||||
|  | ||||
|         for (int index = Fehead.Length + 10; index < byteBlock.Length; ++index) | ||||
|             ByteBlockExtension.WriteBackAddValue(ref byteBlock, (byte)0x33, index);//传输时发送方按字节进行加33H处理,接收方按字节进行减33H处理 | ||||
|         var lenSpan = writerLenAnchor.Rewind(ref byteBlock, out var length); | ||||
|         lenSpan.WriteValue<byte>((byte)(length - 1));//数据域长度 | ||||
|  | ||||
|         int num = 0; | ||||
|         var span = byteBlock.Span; | ||||
|         for (int index = Fehead.Length; index < byteBlock.Length; ++index) | ||||
|         for (int index = 0; index < byteBlock.WrittenCount; ++index) | ||||
|             num += span[index]; | ||||
|         WriterExtension.WriteValue(ref byteBlock, (byte)num);//校验码,总加和 | ||||
|         WriterExtension.WriteValue(ref byteBlock, (byte)0x16);//结束符 | ||||
|   | ||||
| @@ -78,7 +78,7 @@ public class Dlt645_2007Master : DtuServiceDeviceBase | ||||
|     /// <param name="station">表号</param> | ||||
|     /// <param name="cancellationToken">取消令箭</param> | ||||
|     /// <returns></returns> | ||||
|     public async ValueTask<OperResult> FreezeAsync(DateTime dateTime, string station = null, CancellationToken cancellationToken = default) | ||||
|     public ValueTask<OperResult<ReadOnlyMemory<byte>>> FreezeAsync(DateTime dateTime, string station = null, CancellationToken cancellationToken = default) | ||||
|     { | ||||
|         try | ||||
|         { | ||||
| @@ -87,11 +87,11 @@ public class Dlt645_2007Master : DtuServiceDeviceBase | ||||
|             dAddress.SetStation(station ?? Station); | ||||
|             dAddress.DataId = str.HexStringToBytes(); | ||||
|  | ||||
|             return await Dlt645RequestAsync(dAddress, ControlCode.Freeze, FEHead, cancellationToken: cancellationToken).ConfigureAwait(false); | ||||
|             return Dlt645RequestAsync(dAddress, ControlCode.Freeze, FEHead, cancellationToken: cancellationToken); | ||||
|         } | ||||
|         catch (Exception ex) | ||||
|         { | ||||
|             return new OperResult<string>(ex); | ||||
|             return EasyValueTask.FromResult(new OperResult<ReadOnlyMemory<byte>>(ex)); | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @@ -217,7 +217,7 @@ public class Dlt645_2007Master : DtuServiceDeviceBase | ||||
|         } | ||||
|         catch (Exception ex) | ||||
|         { | ||||
|             return new OperResult<byte[]>(ex); | ||||
|             return new OperResult(ex); | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @@ -253,7 +253,7 @@ public class Dlt645_2007Master : DtuServiceDeviceBase | ||||
|         } | ||||
|         catch (Exception ex) | ||||
|         { | ||||
|             return new OperResult<byte[]>(ex); | ||||
|             return new OperResult(ex); | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @@ -291,7 +291,7 @@ public class Dlt645_2007Master : DtuServiceDeviceBase | ||||
|     /// <param name="station">表号</param> | ||||
|     /// <param name="cancellationToken">取消令箭</param> | ||||
|     /// <returns></returns> | ||||
|     public async ValueTask<OperResult> WriteBaudRateAsync(int baudRate, string station = null, CancellationToken cancellationToken = default) | ||||
|     public ValueTask<OperResult<ReadOnlyMemory<byte>>> WriteBaudRateAsync(int baudRate, string station = null, CancellationToken cancellationToken = default) | ||||
|     { | ||||
|         try | ||||
|         { | ||||
| @@ -304,18 +304,18 @@ public class Dlt645_2007Master : DtuServiceDeviceBase | ||||
|                 case 4800: baudRateByte = 0x10; break; | ||||
|                 case 9600: baudRateByte = 0x20; break; | ||||
|                 case 19200: baudRateByte = 0x40; break; | ||||
|                 default: return new OperResult<string>(string.Format(AppResource.BaudRateError, baudRate)); | ||||
|                 default: return EasyValueTask.FromResult(new OperResult<ReadOnlyMemory<byte>>(string.Format(AppResource.BaudRateError, baudRate))); | ||||
|             } | ||||
|  | ||||
|             Dlt645_2007Address dAddress = new(); | ||||
|             dAddress.SetStation(station ?? Station); | ||||
|             dAddress.DataId = new byte[] { baudRateByte }; | ||||
|  | ||||
|             return await Dlt645RequestAsync(dAddress, ControlCode.ReadStation, FEHead, cancellationToken: cancellationToken).ConfigureAwait(false); | ||||
|             return Dlt645RequestAsync(dAddress, ControlCode.ReadStation, FEHead, cancellationToken: cancellationToken); | ||||
|         } | ||||
|         catch (Exception ex) | ||||
|         { | ||||
|             return new OperResult<string>(ex); | ||||
|             return EasyValueTask.FromResult(new OperResult<ReadOnlyMemory<byte>>(ex)); | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @@ -338,7 +338,7 @@ public class Dlt645_2007Master : DtuServiceDeviceBase | ||||
|         } | ||||
|         catch (Exception ex) | ||||
|         { | ||||
|             return new OperResult<string>(ex); | ||||
|             return new OperResult(ex); | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @@ -351,7 +351,7 @@ public class Dlt645_2007Master : DtuServiceDeviceBase | ||||
|     /// <param name="station">station</param> | ||||
|     /// <param name="cancellationToken">取消令箭</param> | ||||
|     /// <returns></returns> | ||||
|     public async ValueTask<OperResult> WritePasswordAsync(byte level, string oldPassword, string newPassword, string station = null, CancellationToken cancellationToken = default) | ||||
|     public ValueTask<OperResult<ReadOnlyMemory<byte>>> WritePasswordAsync(byte level, string oldPassword, string newPassword, string station = null, CancellationToken cancellationToken = default) | ||||
|     { | ||||
|         try | ||||
|         { | ||||
| @@ -371,11 +371,11 @@ public class Dlt645_2007Master : DtuServiceDeviceBase | ||||
|             dAddress.Station = "AAAAAAAAAAAA".HexStringToBytes(); | ||||
|             dAddress.DataId = bytes; | ||||
|  | ||||
|             return await Dlt645RequestAsync(dAddress, ControlCode.WritePassword, FEHead, cancellationToken: cancellationToken).ConfigureAwait(false); | ||||
|             return Dlt645RequestAsync(dAddress, ControlCode.WritePassword, FEHead, cancellationToken: cancellationToken); | ||||
|         } | ||||
|         catch (Exception ex) | ||||
|         { | ||||
|             return new OperResult<string>(ex); | ||||
|             return EasyValueTask.FromResult(new OperResult<ReadOnlyMemory<byte>>(ex)); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|   | ||||
| @@ -27,7 +27,8 @@ public class ModbusAddress : ModbusRequest | ||||
|     public ModbusAddress(ModbusRequest modbusAddress) | ||||
|     { | ||||
|         StartAddress = modbusAddress.StartAddress; | ||||
|         Data = modbusAddress.Data; | ||||
|         MasterWriteDatas = modbusAddress.MasterWriteDatas; | ||||
|         SlaveWriteDatas = modbusAddress.SlaveWriteDatas; | ||||
|         FunctionCode = modbusAddress.FunctionCode; | ||||
|         Length = modbusAddress.Length; | ||||
|         Station = modbusAddress.Station; | ||||
| @@ -37,7 +38,8 @@ public class ModbusAddress : ModbusRequest | ||||
|     public ModbusAddress(ModbusAddress modbusAddress) | ||||
|     { | ||||
|         StartAddress = modbusAddress.StartAddress; | ||||
|         Data = modbusAddress.Data; | ||||
|         MasterWriteDatas = modbusAddress.MasterWriteDatas; | ||||
|         SlaveWriteDatas = modbusAddress.SlaveWriteDatas; | ||||
|         FunctionCode = modbusAddress.FunctionCode; | ||||
|         Length = modbusAddress.Length; | ||||
|         BitIndex = modbusAddress.BitIndex; | ||||
|   | ||||
| @@ -8,6 +8,7 @@ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
| using System.Buffers; | ||||
| using System.ComponentModel.DataAnnotations; | ||||
|  | ||||
| namespace ThingsGateway.Foundation.Modbus; | ||||
| @@ -22,7 +23,12 @@ public class ModbusRequest | ||||
|     /// <summary> | ||||
|     /// 数据 | ||||
|     /// </summary> | ||||
|     public ReadOnlyMemory<byte> Data { get; set; } | ||||
|     public ReadOnlyMemory<byte> MasterWriteDatas { get; set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 当前关联的写入字节数组(Slave端使用) | ||||
|     /// </summary> | ||||
|     internal ReadOnlySequence<byte> SlaveWriteDatas { get; set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 功能码 | ||||
|   | ||||
| @@ -16,96 +16,12 @@ namespace ThingsGateway.Foundation.Modbus; | ||||
| public class ModbusRtuMessage : MessageBase, IResultMessage | ||||
| { | ||||
|     /// <inheritdoc/> | ||||
|     public override int HeaderLength => 3; | ||||
|     public override long HeaderLength => 3; | ||||
|  | ||||
|     public ModbusAddress? Request { get; set; } | ||||
|  | ||||
|     public ModbusResponse Response { get; set; } = new(); | ||||
|  | ||||
|     public override FilterResult CheckBody<TByteBlock>(ref TByteBlock byteBlock) | ||||
|     { | ||||
|         if (Response.ErrorCode != null) | ||||
|         { | ||||
|             if (Request != null) | ||||
|             { | ||||
|                 if (Request.Station == Response.Station) | ||||
|                 { | ||||
|                     return FilterResult.Success; | ||||
|                 } | ||||
|                 else | ||||
|                 { | ||||
|                     return FilterResult.GoOn; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         var pos = byteBlock.Position - HeaderLength; | ||||
|         var crcLen = 0; | ||||
|         var f = Response.FunctionCode > 0x30 ? Response.FunctionCode - 0x30 : Response.FunctionCode; | ||||
|         if (f <= 4) | ||||
|         { | ||||
|             OperCode = 0; | ||||
|             Content = byteBlock.ToArrayTake(BodyLength - 2); | ||||
|             Response.Data = Content; | ||||
|             crcLen = 3 + Response.Length; | ||||
|         } | ||||
|         else if (f == 5 || f == 6) | ||||
|         { | ||||
|             byteBlock.Position = HeaderLength - 1; | ||||
|             Response.StartAddress = ReaderExtension.ReadValue<TByteBlock, ushort>(ref byteBlock, EndianType.Big); | ||||
|             OperCode = 0; | ||||
|             Content = byteBlock.ToArrayTake(BodyLength - 4); | ||||
|             Response.Data = Content; | ||||
|             crcLen = 6; | ||||
|         } | ||||
|         else if (f == 15 || f == 16) | ||||
|         { | ||||
|             byteBlock.Position = HeaderLength - 1; | ||||
|             Response.StartAddress = ReaderExtension.ReadValue<TByteBlock, ushort>(ref byteBlock, EndianType.Big); | ||||
|             Response.Length = ReaderExtension.ReadValue<TByteBlock, ushort>(ref byteBlock, EndianType.Big); | ||||
|             OperCode = 0; | ||||
|             Content = Array.Empty<byte>(); | ||||
|             crcLen = 6; | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             OperCode = 999; | ||||
|             ErrorMessage = AppResource.ModbusError1; | ||||
|             return FilterResult.GoOn; | ||||
|         } | ||||
|         if (crcLen > 0) | ||||
|         { | ||||
|             var crc = CRC16Utils.Crc16Only(byteBlock.Span.Slice(pos, crcLen)); | ||||
|  | ||||
|             //Crc | ||||
|             var checkCrc = byteBlock.Span.Slice(pos + crcLen, 2); | ||||
|             if (checkCrc.SequenceEqual(crc)) | ||||
|             { | ||||
|                 //验证发送/返回站号与功能码 | ||||
|                 //站号验证 | ||||
|                 if (Request != null) | ||||
|                 { | ||||
|                     if (Request.Station != Response.Station) | ||||
|                     { | ||||
|                         OperCode = 999; | ||||
|                         Response.ErrorCode = 1; | ||||
|                         ErrorMessage = string.Format(AppResource.StationNotSame, Request.Station, Response.Station); | ||||
|                         return FilterResult.GoOn; | ||||
|                     } | ||||
|                     if (f > 4 ? Request.WriteFunctionCode != Response.FunctionCode : Request.FunctionCode != Response.FunctionCode) | ||||
|                     { | ||||
|                         OperCode = 999; | ||||
|                         Response.ErrorCode = 1; | ||||
|                         ErrorMessage = string.Format(AppResource.FunctionNotSame, Request.FunctionCode, Response.FunctionCode); | ||||
|                         return FilterResult.GoOn; | ||||
|                     } | ||||
|                 } | ||||
|                 return FilterResult.Success; | ||||
|             } | ||||
|         } | ||||
|         return FilterResult.GoOn; | ||||
|     } | ||||
|  | ||||
|     public override bool CheckHead<TByteBlock>(ref TByteBlock byteBlock) | ||||
|     { | ||||
|         Response.Station = ReaderExtension.ReadValue<TByteBlock, byte>(ref byteBlock); | ||||
| @@ -156,7 +72,93 @@ public class ModbusRtuMessage : MessageBase, IResultMessage | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     public override void SendInfo(ISendMessage sendMessage, ref ValueByteBlock byteBlock) | ||||
|  | ||||
|     public override FilterResult CheckBody<TByteBlock>(ref TByteBlock byteBlock) | ||||
|     { | ||||
|         if (Response.ErrorCode != null) | ||||
|         { | ||||
|             if (Request != null) | ||||
|             { | ||||
|                 if (Request.Station == Response.Station) | ||||
|                 { | ||||
|                     return FilterResult.Success; | ||||
|                 } | ||||
|                 else | ||||
|                 { | ||||
|                     return FilterResult.GoOn; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         var pos = byteBlock.BytesRead - HeaderLength; | ||||
|         var crcLen = 0; | ||||
|         var f = Response.FunctionCode > 0x30 ? Response.FunctionCode - 0x30 : Response.FunctionCode; | ||||
|         if (f <= 4) | ||||
|         { | ||||
|             OperCode = 0; | ||||
|             Content = byteBlock.ToArrayTake(BodyLength - 2); | ||||
|             Response.MasterWriteDatas = Content; | ||||
|             crcLen = 3 + Response.Length; | ||||
|         } | ||||
|         else if (f == 5 || f == 6) | ||||
|         { | ||||
|             byteBlock.BytesRead = HeaderLength - 1; | ||||
|             Response.StartAddress = ReaderExtension.ReadValue<TByteBlock, ushort>(ref byteBlock, EndianType.Big); | ||||
|             OperCode = 0; | ||||
|             Content = byteBlock.ToArrayTake(BodyLength - 4); | ||||
|             Response.MasterWriteDatas = Content; | ||||
|             crcLen = 6; | ||||
|         } | ||||
|         else if (f == 15 || f == 16) | ||||
|         { | ||||
|             byteBlock.BytesRead = HeaderLength - 1; | ||||
|             Response.StartAddress = ReaderExtension.ReadValue<TByteBlock, ushort>(ref byteBlock, EndianType.Big); | ||||
|             Response.Length = ReaderExtension.ReadValue<TByteBlock, ushort>(ref byteBlock, EndianType.Big); | ||||
|             OperCode = 0; | ||||
|             Content = Array.Empty<byte>(); | ||||
|             crcLen = 6; | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             OperCode = 999; | ||||
|             ErrorMessage = AppResource.ModbusError1; | ||||
|             return FilterResult.GoOn; | ||||
|         } | ||||
|         if (crcLen > 0) | ||||
|         { | ||||
|             var crc = CRC16Utils.Crc16Only(byteBlock.TotalSequence.Slice(pos, crcLen)); | ||||
|  | ||||
|             //Crc | ||||
|             var checkCrc = byteBlock.TotalSequence.Slice(pos + crcLen, 2); | ||||
|             if (checkCrc.SequenceEqual(crc)) | ||||
|             { | ||||
|                 //验证发送/返回站号与功能码 | ||||
|                 //站号验证 | ||||
|                 if (Request != null) | ||||
|                 { | ||||
|                     if (Request.Station != Response.Station) | ||||
|                     { | ||||
|                         OperCode = 999; | ||||
|                         Response.ErrorCode = 1; | ||||
|                         ErrorMessage = string.Format(AppResource.StationNotSame, Request.Station, Response.Station); | ||||
|                         return FilterResult.GoOn; | ||||
|                     } | ||||
|                     if (f > 4 ? Request.WriteFunctionCode != Response.FunctionCode : Request.FunctionCode != Response.FunctionCode) | ||||
|                     { | ||||
|                         OperCode = 999; | ||||
|                         Response.ErrorCode = 1; | ||||
|                         ErrorMessage = string.Format(AppResource.FunctionNotSame, Request.FunctionCode, Response.FunctionCode); | ||||
|                         return FilterResult.GoOn; | ||||
|                     } | ||||
|                 } | ||||
|                 return FilterResult.Success; | ||||
|             } | ||||
|         } | ||||
|         return FilterResult.GoOn; | ||||
|     } | ||||
|  | ||||
|  | ||||
|     public override void SendInfo(ISendMessage sendMessage) | ||||
|     { | ||||
|         Request = ((ModbusRtuSend)sendMessage).ModbusAddress; | ||||
|     } | ||||
|   | ||||
| @@ -29,8 +29,9 @@ public class ModbusRtuSend : ISendMessage | ||||
|     public ModbusAddress ModbusAddress { get; } | ||||
|     public int Sign { get; set; } | ||||
|  | ||||
|     public void Build<TByteBlock>(ref TByteBlock byteBlock) where TByteBlock : IByteBlockWriter | ||||
|     public void Build<TByteBlock>(ref TByteBlock byteBlock) where TByteBlock : IBytesWriter | ||||
|     { | ||||
|         var span = byteBlock.GetSpan(512); | ||||
|         var f = ModbusAddress.FunctionCode > 0x30 ? ModbusAddress.FunctionCode - 0x30 : ModbusAddress.FunctionCode; | ||||
|         if (!Read) | ||||
|         { | ||||
| @@ -38,7 +39,7 @@ public class ModbusRtuSend : ISendMessage | ||||
|             { | ||||
|                 ModbusAddress.WriteFunctionCode = (byte)(f == 1 ? 5 : 6); | ||||
|             } | ||||
|             if (ModbusAddress.Data.Length > 2 && ModbusAddress.WriteFunctionCode < 15) | ||||
|             if (ModbusAddress.MasterWriteDatas.Length > 2 && ModbusAddress.WriteFunctionCode < 15) | ||||
|             { | ||||
|                 ModbusAddress.WriteFunctionCode = (byte)(f == 1 ? 15 : 16); | ||||
|             } | ||||
| @@ -63,16 +64,16 @@ public class ModbusRtuSend : ISendMessage | ||||
|             WriterExtension.WriteValue(ref byteBlock, (byte)ModbusAddress.Station); | ||||
|             WriterExtension.WriteValue(ref byteBlock, (byte)ModbusAddress.WriteFunctionCode); | ||||
|             WriterExtension.WriteValue(ref byteBlock, (ushort)ModbusAddress.StartAddress, EndianType.Big); | ||||
|             byteBlock.Write(ModbusAddress.Data.Span); | ||||
|             byteBlock.Write(ModbusAddress.MasterWriteDatas.Span); | ||||
|         } | ||||
|         else if (wf == 15 || wf == 16) | ||||
|         { | ||||
|             var data = ModbusAddress.Data.ArrayExpandToLengthEven().Span; | ||||
|             var data = ModbusAddress.MasterWriteDatas.ArrayExpandToLengthEven().Span; | ||||
|             WriterExtension.WriteValue(ref byteBlock, (ushort)(data.Length + 7), EndianType.Big); | ||||
|             WriterExtension.WriteValue(ref byteBlock, (byte)ModbusAddress.Station); | ||||
|             WriterExtension.WriteValue(ref byteBlock, (byte)ModbusAddress.WriteFunctionCode); | ||||
|             WriterExtension.WriteValue(ref byteBlock, (ushort)ModbusAddress.StartAddress, EndianType.Big); | ||||
|             var len = (ushort)Math.Ceiling(wf == 15 ? ModbusAddress.Data.Length * 8 : ModbusAddress.Data.Length / 2.0); | ||||
|             var len = (ushort)Math.Ceiling(wf == 15 ? ModbusAddress.MasterWriteDatas.Length * 8 : ModbusAddress.MasterWriteDatas.Length / 2.0); | ||||
|             WriterExtension.WriteValue(ref byteBlock, len, EndianType.Big); | ||||
|             WriterExtension.WriteValue(ref byteBlock, (byte)(len * 2)); | ||||
|             byteBlock.Write(data); | ||||
| @@ -81,6 +82,7 @@ public class ModbusRtuSend : ISendMessage | ||||
|         { | ||||
|             throw new System.InvalidOperationException(AppResource.ModbusError1); | ||||
|         } | ||||
|         byteBlock.Write(CRC16Utils.Crc16Only(byteBlock.Span)); | ||||
|         var crclen = byteBlock.WrittenCount; | ||||
|         byteBlock.Write(CRC16Utils.Crc16Only(span.Slice(0, (int)crclen))); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -16,74 +16,14 @@ namespace ThingsGateway.Foundation.Modbus; | ||||
| public class ModbusTcpMessage : MessageBase, IResultMessage | ||||
| { | ||||
|     /// <inheritdoc/> | ||||
|     public override int HeaderLength => 8; | ||||
|     public override long HeaderLength => 8; | ||||
|  | ||||
|     public ModbusResponse Response { get; set; } = new(); | ||||
|  | ||||
|     public override FilterResult CheckBody<TByteBlock>(ref TByteBlock byteBlock) | ||||
|     { | ||||
|         var f = Response.FunctionCode > 0x30 ? Response.FunctionCode - 0x30 : Response.FunctionCode; | ||||
|         if (error) | ||||
|         { | ||||
|             Response.ErrorCode = ReaderExtension.ReadValue<TByteBlock, byte>(ref byteBlock); | ||||
|             OperCode = Response.ErrorCode; | ||||
|             ErrorMessage = ModbusHelper.GetDescriptionByErrorCode(Response.ErrorCode.Value); | ||||
|             ErrorType = ErrorTypeEnum.DeviceError; | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             Response.ErrorCode = null; | ||||
|             if (f <= 4) | ||||
|             { | ||||
|                 Response.Length = ReaderExtension.ReadValue<TByteBlock, byte>(ref byteBlock); | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 Response.StartAddress = ReaderExtension.ReadValue<TByteBlock, ushort>(ref byteBlock); | ||||
|             } | ||||
|         } | ||||
|         if (Response.ErrorCode != null) | ||||
|         { | ||||
|             return FilterResult.Success; | ||||
|         } | ||||
|  | ||||
|         if (f <= 4) | ||||
|         { | ||||
|             OperCode = 0; | ||||
|             Content = byteBlock.ToArrayTake(BodyLength - 1); | ||||
|             Response.Data = Content; | ||||
|             return FilterResult.Success; | ||||
|         } | ||||
|         else if (f == 5 || f == 6) | ||||
|         { | ||||
|             byteBlock.Position = HeaderLength - 1; | ||||
|             Response.StartAddress = ReaderExtension.ReadValue<TByteBlock, ushort>(ref byteBlock); | ||||
|             OperCode = 0; | ||||
|             Content = byteBlock.ToArrayTake(BodyLength - 2); | ||||
|             Response.Data = Content; | ||||
|             return FilterResult.Success; | ||||
|         } | ||||
|         else if (f == 15 || f == 16) | ||||
|         { | ||||
|             byteBlock.Position = HeaderLength - 1; | ||||
|             Response.StartAddress = ReaderExtension.ReadValue<TByteBlock, ushort>(ref byteBlock, EndianType.Big); | ||||
|             Response.Length = ReaderExtension.ReadValue<TByteBlock, ushort>(ref byteBlock, EndianType.Big); | ||||
|             OperCode = 0; | ||||
|             Content = Array.Empty<byte>(); | ||||
|             return FilterResult.Success; | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             OperCode = 999; | ||||
|             ErrorMessage = AppResource.ModbusError1; | ||||
|         } | ||||
|         return FilterResult.GoOn; | ||||
|     } | ||||
|     bool error = false; | ||||
|     public override bool CheckHead<TByteBlock>(ref TByteBlock byteBlock) | ||||
|     { | ||||
|         Sign = ReaderExtension.ReadValue<TByteBlock, ushort>(ref byteBlock, EndianType.Big); | ||||
|         byteBlock.Position += 2; | ||||
|         byteBlock.BytesRead += 2; | ||||
|         BodyLength = ReaderExtension.ReadValue<TByteBlock, ushort>(ref byteBlock, EndianType.Big) - 2; | ||||
|         Response.Station = ReaderExtension.ReadValue<TByteBlock, byte>(ref byteBlock); | ||||
|         Response.FunctionCode = ReaderExtension.ReadValue<TByteBlock, byte>(ref byteBlock); | ||||
| @@ -98,4 +38,57 @@ public class ModbusTcpMessage : MessageBase, IResultMessage | ||||
|         } | ||||
|         return true; | ||||
|     } | ||||
|  | ||||
|  | ||||
|     public override FilterResult CheckBody<TByteBlock>(ref TByteBlock byteBlock) | ||||
|     { | ||||
|         var f = Response.FunctionCode > 0x30 ? Response.FunctionCode - 0x30 : Response.FunctionCode; | ||||
|         if (error) | ||||
|         { | ||||
|             Response.ErrorCode = ReaderExtension.ReadValue<TByteBlock, byte>(ref byteBlock); | ||||
|             OperCode = Response.ErrorCode; | ||||
|             ErrorMessage = ModbusHelper.GetDescriptionByErrorCode(Response.ErrorCode.Value); | ||||
|             ErrorType = ErrorTypeEnum.DeviceError; | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             Response.ErrorCode = null; | ||||
|         } | ||||
|         if (Response.ErrorCode != null) | ||||
|         { | ||||
|             return FilterResult.Success; | ||||
|         } | ||||
|  | ||||
|         if (f <= 4) | ||||
|         { | ||||
|             OperCode = 0; | ||||
|             Response.Length = ReaderExtension.ReadValue<TByteBlock, byte>(ref byteBlock); | ||||
|             Content = byteBlock.ToArrayTake(BodyLength - 1); | ||||
|             Response.MasterWriteDatas = Content; | ||||
|             return FilterResult.Success; | ||||
|         } | ||||
|         else if (f == 5 || f == 6) | ||||
|         { | ||||
|             Response.StartAddress = ReaderExtension.ReadValue<TByteBlock, ushort>(ref byteBlock, EndianType.Big); | ||||
|             OperCode = 0; | ||||
|             Content = byteBlock.ToArrayTake(BodyLength - 2); | ||||
|             Response.MasterWriteDatas = Content; | ||||
|             return FilterResult.Success; | ||||
|         } | ||||
|         else if (f == 15 || f == 16) | ||||
|         { | ||||
|             Response.StartAddress = ReaderExtension.ReadValue<TByteBlock, ushort>(ref byteBlock, EndianType.Big); | ||||
|             OperCode = 0; | ||||
|             Response.Length = ReaderExtension.ReadValue<TByteBlock, ushort>(ref byteBlock, EndianType.Big); | ||||
|             Content = Array.Empty<byte>(); | ||||
|             return FilterResult.Success; | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             OperCode = 999; | ||||
|             ErrorMessage = AppResource.ModbusError1; | ||||
|         } | ||||
|         return FilterResult.GoOn; | ||||
|     } | ||||
|     bool error = false; | ||||
| } | ||||
|   | ||||
| @@ -42,7 +42,7 @@ public class ModbusTcpSend : ISendMessage | ||||
|     /// </summary> | ||||
|     public ushort TransactionId { get; private set; } | ||||
|  | ||||
|     public void Build<TByteBlock>(ref TByteBlock byteBlock) where TByteBlock : IByteBlockWriter | ||||
|     public void Build<TByteBlock>(ref TByteBlock byteBlock) where TByteBlock : IBytesWriter | ||||
|     { | ||||
|         TransactionId = (ushort)Sign; | ||||
|         var f = ModbusAddress.FunctionCode > 0x30 ? ModbusAddress.FunctionCode - 0x30 : ModbusAddress.FunctionCode; | ||||
| @@ -52,7 +52,7 @@ public class ModbusTcpSend : ISendMessage | ||||
|             { | ||||
|                 ModbusAddress.WriteFunctionCode = (byte)(f == 1 ? 5 : 6); | ||||
|             } | ||||
|             if (ModbusAddress.Data.Length > 2 && ModbusAddress.WriteFunctionCode < 15) | ||||
|             if (ModbusAddress.MasterWriteDatas.Length > 2 && ModbusAddress.WriteFunctionCode < 15) | ||||
|             { | ||||
|                 ModbusAddress.WriteFunctionCode = (byte)(f == 1 ? 15 : 16); | ||||
|             } | ||||
| @@ -82,16 +82,16 @@ public class ModbusTcpSend : ISendMessage | ||||
|             WriterExtension.WriteValue(ref byteBlock, (byte)ModbusAddress.Station); | ||||
|             WriterExtension.WriteValue(ref byteBlock, (byte)ModbusAddress.WriteFunctionCode); | ||||
|             WriterExtension.WriteValue(ref byteBlock, (ushort)ModbusAddress.StartAddress, EndianType.Big); | ||||
|             byteBlock.Write(ModbusAddress.Data.Span); | ||||
|             byteBlock.Write(ModbusAddress.MasterWriteDatas.Span); | ||||
|         } | ||||
|         else if (wf == 15 || wf == 16) | ||||
|         { | ||||
|             var data = ModbusAddress.Data.ArrayExpandToLengthEven().Span; | ||||
|             var data = ModbusAddress.MasterWriteDatas.ArrayExpandToLengthEven().Span; | ||||
|             WriterExtension.WriteValue(ref byteBlock, (ushort)(data.Length + 7), EndianType.Big); | ||||
|             WriterExtension.WriteValue(ref byteBlock, (byte)ModbusAddress.Station); | ||||
|             WriterExtension.WriteValue(ref byteBlock, (byte)ModbusAddress.WriteFunctionCode); | ||||
|             WriterExtension.WriteValue(ref byteBlock, (ushort)ModbusAddress.StartAddress, EndianType.Big); | ||||
|             var len = (ushort)Math.Ceiling(wf == 15 ? ModbusAddress.Data.Length * 8 : ModbusAddress.Data.Length / 2.0); | ||||
|             var len = (ushort)Math.Ceiling(wf == 15 ? ModbusAddress.MasterWriteDatas.Length * 8 : ModbusAddress.MasterWriteDatas.Length / 2.0); | ||||
|             WriterExtension.WriteValue(ref byteBlock, len, EndianType.Big); | ||||
|             WriterExtension.WriteValue(ref byteBlock, (byte)(len * 2)); | ||||
|             byteBlock.Write(data); | ||||
|   | ||||
| @@ -167,7 +167,7 @@ public partial class ModbusMaster : DtuServiceDeviceBase, IModbusAddress | ||||
|         try | ||||
|         { | ||||
|             var mAddress = GetModbusAddress(address, Station); | ||||
|             mAddress.Data = value; | ||||
|             mAddress.MasterWriteDatas = value; | ||||
|  | ||||
|             if (mAddress.BitIndex == null) | ||||
|             { | ||||
| @@ -185,7 +185,7 @@ public partial class ModbusMaster : DtuServiceDeviceBase, IModbusAddress | ||||
|                 else | ||||
|                     writeValye[0] = v; | ||||
|  | ||||
|                 mAddress.Data = writeValye; | ||||
|                 mAddress.MasterWriteDatas = writeValye; | ||||
|                 return await ModbusRequestAsync(mAddress, false, cancellationToken).ConfigureAwait(false); | ||||
|             } | ||||
|             else | ||||
| @@ -212,13 +212,13 @@ public partial class ModbusMaster : DtuServiceDeviceBase, IModbusAddress | ||||
|             if (value.Length > 1 && (mAddress.FunctionCode == 1 || mAddress.FunctionCode == 0x31)) | ||||
|             { | ||||
|                 mAddress.WriteFunctionCode = 15; | ||||
|                 mAddress.Data = value.Span.BoolArrayToByte(); | ||||
|                 mAddress.MasterWriteDatas = value.Span.BoolArrayToByte(); | ||||
|                 return await ModbusRequestAsync(mAddress, false, cancellationToken).ConfigureAwait(false); | ||||
|             } | ||||
|             else if (mAddress.BitIndex == null) | ||||
|             { | ||||
|                 var span = value.Span; | ||||
|                 mAddress.Data = span[0] ? new byte[2] { 255, 0 } : [0, 0]; | ||||
|                 mAddress.MasterWriteDatas = span[0] ? new byte[2] { 255, 0 } : [0, 0]; | ||||
|                 return await ModbusRequestAsync(mAddress, false, cancellationToken).ConfigureAwait(false); | ||||
|             } | ||||
|             else | ||||
| @@ -234,7 +234,7 @@ public partial class ModbusMaster : DtuServiceDeviceBase, IModbusAddress | ||||
|                     { | ||||
|                         writeData = writeData.SetBit(mAddress.BitIndex.Value + i, span[i]); | ||||
|                     } | ||||
|                     mAddress.Data = ThingsGatewayBitConverter.GetBytes(writeData); | ||||
|                     mAddress.MasterWriteDatas = ThingsGatewayBitConverter.GetBytes(writeData); | ||||
|                     return await ModbusRequestAsync(mAddress, false, cancellationToken).ConfigureAwait(false); | ||||
|                 } | ||||
|                 else | ||||
|   | ||||
| @@ -8,6 +8,8 @@ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
| using System.Buffers; | ||||
|  | ||||
| namespace ThingsGateway.Foundation.Modbus; | ||||
|  | ||||
| /// <summary> | ||||
| @@ -18,10 +20,10 @@ public class ModbusRtuSlaveMessage : MessageBase, IResultMessage | ||||
|     /// <summary> | ||||
|     /// 当前关联的字节数组 | ||||
|     /// </summary> | ||||
|     public ReadOnlyMemory<byte> Bytes { get; set; } | ||||
|     public ReadOnlySequence<byte> Sequences { get; set; } | ||||
|  | ||||
|     /// <inheritdoc/> | ||||
|     public override int HeaderLength => 7; | ||||
|     public override long HeaderLength => 7; | ||||
|  | ||||
|     public ModbusRequest Request { get; set; } = new(); | ||||
|  | ||||
| @@ -46,13 +48,13 @@ public class ModbusRtuSlaveMessage : MessageBase, IResultMessage | ||||
|         } | ||||
|         else if (f == 5) | ||||
|         { | ||||
|             Request.Data = byteBlock.Memory.Slice(byteBlock.Position, 1); | ||||
|             Request.SlaveWriteDatas = byteBlock.Sequence.Slice(0, 1); | ||||
|             BodyLength = 1; | ||||
|             return true; | ||||
|         } | ||||
|         else if (f == 6) | ||||
|         { | ||||
|             Request.Data = byteBlock.Memory.Slice(byteBlock.Position, 2); | ||||
|             Request.SlaveWriteDatas = byteBlock.Sequence.Slice(0, 2); | ||||
|             BodyLength = 1; | ||||
|             return true; | ||||
|         } | ||||
| @@ -73,26 +75,27 @@ public class ModbusRtuSlaveMessage : MessageBase, IResultMessage | ||||
|  | ||||
|     public override FilterResult CheckBody<TByteBlock>(ref TByteBlock byteBlock) | ||||
|     { | ||||
|         var pos = byteBlock.Position - HeaderLength; | ||||
|         var crcLen = 0; | ||||
|         Bytes = byteBlock.Memory.Slice(pos, HeaderLength + BodyLength); | ||||
|         var pos = byteBlock.BytesRead - HeaderLength; | ||||
|         long crcLen = 0; | ||||
|         Sequences = byteBlock.TotalSequence.Slice(pos, HeaderLength + BodyLength); | ||||
|  | ||||
|         var f = Request.FunctionCode > 0x30 ? Request.FunctionCode - 0x30 : Request.FunctionCode; | ||||
|         if (f == 15) | ||||
|         { | ||||
|             Request.Data = byteBlock.Memory.Slice(byteBlock.Position, Request.Length).Span.ByteToBoolArray(Request.Length).BoolToByte(); | ||||
|             Request.SlaveWriteDatas = new ReadOnlySequence<byte>(byteBlock.TotalSequence.Slice(byteBlock.BytesRead, Request.Length).ByteToBoolArray(Request.Length).BoolToByte()); | ||||
|         } | ||||
|         else if (f == 16) | ||||
|         { | ||||
|             Request.Data = byteBlock.Memory.Slice(byteBlock.Position, Request.Length); | ||||
|  | ||||
|             Request.SlaveWriteDatas = byteBlock.TotalSequence.Slice(byteBlock.BytesRead, Request.Length); | ||||
|         } | ||||
|  | ||||
|         crcLen = HeaderLength + BodyLength - 2; | ||||
|  | ||||
|         var crc = CRC16Utils.Crc16Only(byteBlock.Span.Slice(pos, crcLen)); | ||||
|         var crc = CRC16Utils.Crc16Only(byteBlock.TotalSequence.Slice(pos, crcLen)); | ||||
|  | ||||
|         //Crc | ||||
|         var checkCrc = byteBlock.Span.Slice(pos + crcLen, 2); | ||||
|         var checkCrc = byteBlock.TotalSequence.Slice(pos + crcLen, 2); | ||||
|         if (checkCrc.SequenceEqual(crc)) | ||||
|         { | ||||
|             OperCode = 0; | ||||
|   | ||||
| @@ -8,6 +8,8 @@ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
| using System.Buffers; | ||||
|  | ||||
| namespace ThingsGateway.Foundation.Modbus; | ||||
|  | ||||
| /// <summary> | ||||
| @@ -18,17 +20,17 @@ public class ModbusTcpSlaveMessage : MessageBase, IResultMessage | ||||
|     /// <summary> | ||||
|     /// 当前关联的字节数组 | ||||
|     /// </summary> | ||||
|     public ReadOnlyMemory<byte> Bytes { get; set; } | ||||
|     public ReadOnlySequence<byte> Sequences { get; set; } | ||||
|  | ||||
|     /// <inheritdoc/> | ||||
|     public override int HeaderLength => 12; | ||||
|     public override long HeaderLength => 12; | ||||
|  | ||||
|     public ModbusRequest Request { get; set; } = new(); | ||||
|  | ||||
|     public override bool CheckHead<TByteBlock>(ref TByteBlock byteBlock) | ||||
|     { | ||||
|         Sign = ReaderExtension.ReadValue<TByteBlock, ushort>(ref byteBlock, EndianType.Big); | ||||
|         byteBlock.Position += 2; | ||||
|         byteBlock.BytesRead += 2; | ||||
|         BodyLength = ReaderExtension.ReadValue<TByteBlock, ushort>(ref byteBlock, EndianType.Big) - 6; | ||||
|         Request.Station = ReaderExtension.ReadValue<TByteBlock, byte>(ref byteBlock); | ||||
|         Request.FunctionCode = ReaderExtension.ReadValue<TByteBlock, byte>(ref byteBlock); | ||||
| @@ -48,12 +50,12 @@ public class ModbusTcpSlaveMessage : MessageBase, IResultMessage | ||||
|         } | ||||
|         else if (f == 5) | ||||
|         { | ||||
|             Request.Data = byteBlock.Memory.Slice(byteBlock.Position, 1); | ||||
|             Request.SlaveWriteDatas = byteBlock.Sequence.Slice(0, 1); | ||||
|             return true; | ||||
|         } | ||||
|         else if (f == 6) | ||||
|         { | ||||
|             Request.Data = byteBlock.Memory.Slice(byteBlock.Position, 2); | ||||
|             Request.SlaveWriteDatas = byteBlock.Sequence.Slice(0, 2); | ||||
|             return true; | ||||
|         } | ||||
|         else if (f == 15) | ||||
| @@ -71,20 +73,20 @@ public class ModbusTcpSlaveMessage : MessageBase, IResultMessage | ||||
|  | ||||
|     public override FilterResult CheckBody<TByteBlock>(ref TByteBlock byteBlock) | ||||
|     { | ||||
|         var pos = byteBlock.Position - HeaderLength; | ||||
|         Bytes = byteBlock.Memory.Slice(pos, HeaderLength + BodyLength); | ||||
|         var pos = byteBlock.BytesRead - HeaderLength; | ||||
|         Sequences = byteBlock.TotalSequence.Slice(pos, HeaderLength + BodyLength); | ||||
|  | ||||
|         var f = Request.FunctionCode > 0x30 ? Request.FunctionCode - 0x30 : Request.FunctionCode; | ||||
|  | ||||
|         if (f == 15) | ||||
|         { | ||||
|             byteBlock.Position += 1; | ||||
|             Request.Data = byteBlock.Memory.Slice(byteBlock.Position, Request.Length).Span.ByteToBoolArray(Request.Length).BoolToByte(); | ||||
|             byteBlock.BytesRead += 1; | ||||
|             Request.SlaveWriteDatas = new ReadOnlySequence<byte>(byteBlock.Sequence.Slice(0, Request.Length).ByteToBoolArray(Request.Length).BoolToByte()); | ||||
|         } | ||||
|         else if (f == 16) | ||||
|         { | ||||
|             byteBlock.Position += 1; | ||||
|             Request.Data = byteBlock.Memory.Slice(byteBlock.Position, Request.Length); | ||||
|             byteBlock.BytesRead += 1; | ||||
|             Request.SlaveWriteDatas = byteBlock.Sequence.Slice(0, Request.Length); | ||||
|         } | ||||
|  | ||||
|         OperCode = 0; | ||||
|   | ||||
| @@ -8,6 +8,7 @@ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
| using System.Buffers; | ||||
| using System.Collections.Concurrent; | ||||
|  | ||||
| using TouchSocket.Sockets; | ||||
| @@ -298,26 +299,29 @@ public class ModbusSlave : DeviceBase, IModbusAddress | ||||
|                     { | ||||
|                         case 2: | ||||
|                             ModbusServer02ByteBlock.Position = mAddress.StartAddress; | ||||
|                             ModbusServer02ByteBlock.Write(mAddress.Data.Span); | ||||
|                             ByteBlockExtension.Write(ref ModbusServer02ByteBlock, mAddress.SlaveWriteDatas); | ||||
|                             return new(); | ||||
|  | ||||
|                         case 1: | ||||
|                         case 5: | ||||
|                         case 15: | ||||
|                             ModbusServer01ByteBlock.Position = mAddress.StartAddress; | ||||
|                             ModbusServer01ByteBlock.Write(mAddress.Data.Span); | ||||
|                             ByteBlockExtension.Write(ref ModbusServer01ByteBlock, mAddress.SlaveWriteDatas); | ||||
|  | ||||
|                             return new(); | ||||
|  | ||||
|                         case 4: | ||||
|                             ModbusServer04ByteBlock.Position = mAddress.StartAddress * RegisterByteLength; | ||||
|                             ModbusServer04ByteBlock.Write(mAddress.Data.Span); | ||||
|                             ByteBlockExtension.Write(ref ModbusServer04ByteBlock, mAddress.SlaveWriteDatas); | ||||
|  | ||||
|                             return new(); | ||||
|  | ||||
|                         case 3: | ||||
|                         case 6: | ||||
|                         case 16: | ||||
|                             ModbusServer03ByteBlock.Position = mAddress.StartAddress * RegisterByteLength; | ||||
|                             ModbusServer03ByteBlock.Write(mAddress.Data.Span); | ||||
|                             ByteBlockExtension.Write(ref ModbusServer03ByteBlock, mAddress.SlaveWriteDatas); | ||||
|  | ||||
|                             return new(); | ||||
|                     } | ||||
|                 } | ||||
| @@ -386,7 +390,7 @@ public class ModbusSlave : DeviceBase, IModbusAddress | ||||
|         { | ||||
|             await EasyValueTask.CompletedTask.ConfigureAwait(false); | ||||
|             var mAddress = GetModbusAddress(address, Station); | ||||
|             mAddress.Data = value; | ||||
|             mAddress.SlaveWriteDatas = new(value); | ||||
|             return ModbusRequest(mAddress, false, cancellationToken); | ||||
|         } | ||||
|         catch (Exception ex) | ||||
| @@ -404,7 +408,7 @@ public class ModbusSlave : DeviceBase, IModbusAddress | ||||
|             var mAddress = GetModbusAddress(address, Station); | ||||
|             if (mAddress.IsBitFunction) | ||||
|             { | ||||
|                 mAddress.Data = value.Span.BoolToByte(); | ||||
|                 mAddress.SlaveWriteDatas = new(value.Span.BoolToByte()); | ||||
|                 ModbusRequest(mAddress, false, cancellationToken); | ||||
|                 return OperResult.Success; | ||||
|             } | ||||
| @@ -421,7 +425,7 @@ public class ModbusSlave : DeviceBase, IModbusAddress | ||||
|                     { | ||||
|                         writeData = writeData.SetBit(mAddress.BitIndex.Value + i, span[i]); | ||||
|                     } | ||||
|                     mAddress.Data = ThingsGatewayBitConverter.GetBytes(writeData); | ||||
|                     mAddress.SlaveWriteDatas = new(ThingsGatewayBitConverter.GetBytes(writeData)); | ||||
|                     ModbusRequest(mAddress, false, cancellationToken); | ||||
|                     return OperResult.Success; | ||||
|                 } | ||||
| @@ -443,7 +447,7 @@ public class ModbusSlave : DeviceBase, IModbusAddress | ||||
|         var requestInfo = e.RequestInfo; | ||||
|         bool modbusRtu = false; | ||||
|         ModbusRequest modbusRequest = default; | ||||
|         ReadOnlyMemory<byte> Bytes = default; | ||||
|         ReadOnlySequence<byte> readOnlySequences = default; | ||||
|         //接收外部报文 | ||||
|         if (requestInfo is ModbusRtuSlaveMessage modbusRtuSlaveMessage) | ||||
|         { | ||||
| @@ -452,7 +456,7 @@ public class ModbusSlave : DeviceBase, IModbusAddress | ||||
|                 return; | ||||
|             } | ||||
|             modbusRequest = modbusRtuSlaveMessage.Request; | ||||
|             Bytes = modbusRtuSlaveMessage.Bytes; | ||||
|             readOnlySequences = modbusRtuSlaveMessage.Sequences; | ||||
|             modbusRtu = true; | ||||
|         } | ||||
|         else if (requestInfo is ModbusTcpSlaveMessage modbusTcpSlaveMessage) | ||||
| @@ -462,7 +466,7 @@ public class ModbusSlave : DeviceBase, IModbusAddress | ||||
|                 return; | ||||
|             } | ||||
|             modbusRequest = modbusTcpSlaveMessage.Request; | ||||
|             Bytes = modbusTcpSlaveMessage.Bytes; | ||||
|             readOnlySequences = modbusTcpSlaveMessage.Sequences; | ||||
|             modbusRtu = false; | ||||
|         } | ||||
|         else | ||||
| @@ -486,7 +490,7 @@ public class ModbusSlave : DeviceBase, IModbusAddress | ||||
|                 { | ||||
|                     if (modbusRtu) | ||||
|                     { | ||||
|                         byteBlock.Write(Bytes.Slice(0, 2).Span); | ||||
|                         ByteBlockExtension.Write(ref byteBlock, readOnlySequences.Slice(0, 2)); | ||||
|                         if (modbusRequest.IsBitFunction) | ||||
|                         { | ||||
|                             var bitdata = data.Content.Span.ByteToBool().AsSpan().BoolArrayToByte(); | ||||
| @@ -504,7 +508,7 @@ public class ModbusSlave : DeviceBase, IModbusAddress | ||||
|                     } | ||||
|                     else | ||||
|                     { | ||||
|                         byteBlock.Write(Bytes.Slice(0, 8).Span); | ||||
|                         ByteBlockExtension.Write(ref byteBlock, readOnlySequences.Slice(0, 8)); | ||||
|                         if (modbusRequest.IsBitFunction) | ||||
|                         { | ||||
|                             var bitdata = data.Content.Span.ByteToBool().AsSpan().BoolArrayToByte(); | ||||
| @@ -517,13 +521,13 @@ public class ModbusSlave : DeviceBase, IModbusAddress | ||||
|                             WriterExtension.WriteValue(ref byteBlock, (byte)data.Content.Length); | ||||
|                             byteBlock.Write(data.Content.Span); | ||||
|                         } | ||||
|                         ByteBlockExtension.WriteBackValue(ref byteBlock, (byte)(byteBlock.Length - 6), 5); | ||||
|                         ByteBlockExtension.WriteBackValue(ref byteBlock, (byte)(byteBlock.Length - 6), EndianType.Big, 5); | ||||
|                         await ReturnData(client, byteBlock.Memory, e).ConfigureAwait(false); | ||||
|                     } | ||||
|                 } | ||||
|                 catch | ||||
|                 { | ||||
|                     await WriteError(modbusRtu, client, Bytes, e).ConfigureAwait(false); | ||||
|                     await WriteError(modbusRtu, client, readOnlySequences, e).ConfigureAwait(false); | ||||
|                 } | ||||
|                 finally | ||||
|                 { | ||||
| @@ -532,7 +536,7 @@ public class ModbusSlave : DeviceBase, IModbusAddress | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 await WriteError(modbusRtu, client, Bytes, e).ConfigureAwait(false);//返回错误码 | ||||
|                 await WriteError(modbusRtu, client, readOnlySequences, e).ConfigureAwait(false);//返回错误码 | ||||
|             } | ||||
|         } | ||||
|         else//写入 | ||||
| @@ -546,21 +550,21 @@ public class ModbusSlave : DeviceBase, IModbusAddress | ||||
|                     // 接收外部写入时,传出变量地址/写入字节组/转换规则/客户端 | ||||
|                     if ((await WriteData(modbusAddress, ThingsGatewayBitConverter, client).ConfigureAwait(false)).IsSuccess) | ||||
|                     { | ||||
|                         await WriteSuccess(modbusRtu, client, Bytes, e).ConfigureAwait(false); | ||||
|                         await WriteSuccess(modbusRtu, client, readOnlySequences, e).ConfigureAwait(false); | ||||
|                         if (IsWriteMemory) | ||||
|                         { | ||||
|                             var result = ModbusRequest(modbusRequest, false); | ||||
|                             if (result.IsSuccess) | ||||
|                                 await WriteSuccess(modbusRtu, client, Bytes, e).ConfigureAwait(false); | ||||
|                                 await WriteSuccess(modbusRtu, client, readOnlySequences, e).ConfigureAwait(false); | ||||
|                             else | ||||
|                                 await WriteError(modbusRtu, client, Bytes, e).ConfigureAwait(false); | ||||
|                                 await WriteError(modbusRtu, client, readOnlySequences, e).ConfigureAwait(false); | ||||
|                         } | ||||
|                         else | ||||
|                             await WriteSuccess(modbusRtu, client, Bytes, e).ConfigureAwait(false); | ||||
|                             await WriteSuccess(modbusRtu, client, readOnlySequences, e).ConfigureAwait(false); | ||||
|                     } | ||||
|                     else | ||||
|                     { | ||||
|                         await WriteError(modbusRtu, client, Bytes, e).ConfigureAwait(false); | ||||
|                         await WriteError(modbusRtu, client, readOnlySequences, e).ConfigureAwait(false); | ||||
|                     } | ||||
|                 } | ||||
|                 else | ||||
| @@ -569,11 +573,11 @@ public class ModbusSlave : DeviceBase, IModbusAddress | ||||
|                     var result = ModbusRequest(modbusRequest, false); | ||||
|                     if (result.IsSuccess) | ||||
|                     { | ||||
|                         await WriteSuccess(modbusRtu, client, Bytes, e).ConfigureAwait(false); | ||||
|                         await WriteSuccess(modbusRtu, client, readOnlySequences, e).ConfigureAwait(false); | ||||
|                     } | ||||
|                     else | ||||
|                     { | ||||
|                         await WriteError(modbusRtu, client, Bytes, e).ConfigureAwait(false); | ||||
|                         await WriteError(modbusRtu, client, readOnlySequences, e).ConfigureAwait(false); | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
| @@ -589,16 +593,16 @@ public class ModbusSlave : DeviceBase, IModbusAddress | ||||
|                         { | ||||
|                             var result = ModbusRequest(modbusRequest, false); | ||||
|                             if (result.IsSuccess) | ||||
|                                 await WriteSuccess(modbusRtu, client, Bytes, e).ConfigureAwait(false); | ||||
|                                 await WriteSuccess(modbusRtu, client, readOnlySequences, e).ConfigureAwait(false); | ||||
|                             else | ||||
|                                 await WriteError(modbusRtu, client, Bytes, e).ConfigureAwait(false); | ||||
|                                 await WriteError(modbusRtu, client, readOnlySequences, e).ConfigureAwait(false); | ||||
|                         } | ||||
|                         else | ||||
|                             await WriteSuccess(modbusRtu, client, Bytes, e).ConfigureAwait(false); | ||||
|                             await WriteSuccess(modbusRtu, client, readOnlySequences, e).ConfigureAwait(false); | ||||
|                     } | ||||
|                     else | ||||
|                     { | ||||
|                         await WriteError(modbusRtu, client, Bytes, e).ConfigureAwait(false); | ||||
|                         await WriteError(modbusRtu, client, readOnlySequences, e).ConfigureAwait(false); | ||||
|                     } | ||||
|                 } | ||||
|                 else | ||||
| @@ -606,11 +610,11 @@ public class ModbusSlave : DeviceBase, IModbusAddress | ||||
|                     var result = ModbusRequest(modbusRequest, false); | ||||
|                     if (result.IsSuccess) | ||||
|                     { | ||||
|                         await WriteSuccess(modbusRtu, client, Bytes, e).ConfigureAwait(false); | ||||
|                         await WriteSuccess(modbusRtu, client, readOnlySequences, e).ConfigureAwait(false); | ||||
|                     } | ||||
|                     else | ||||
|                     { | ||||
|                         await WriteError(modbusRtu, client, Bytes, e).ConfigureAwait(false); | ||||
|                         await WriteError(modbusRtu, client, readOnlySequences, e).ConfigureAwait(false); | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
| @@ -627,24 +631,24 @@ public class ModbusSlave : DeviceBase, IModbusAddress | ||||
|             await client.SendAsync(sendData, client.ClosedToken).ConfigureAwait(false); | ||||
|     } | ||||
|  | ||||
|     private async Task WriteError(bool modbusRtu, IClientChannel client, ReadOnlyMemory<byte> bytes, ReceivedDataEventArgs e) | ||||
|     private async Task WriteError(bool modbusRtu, IClientChannel client, ReadOnlySequence<byte> bytes, ReceivedDataEventArgs e) | ||||
|     { | ||||
|         ValueByteBlock byteBlock = new(20); | ||||
|         try | ||||
|         { | ||||
|             if (modbusRtu) | ||||
|             { | ||||
|                 byteBlock.Write(bytes.Slice(0, 2).Span); | ||||
|                 ByteBlockExtension.Write(ref byteBlock, bytes.Slice(0, 2)); | ||||
|                 WriterExtension.WriteValue(ref byteBlock, (byte)1); | ||||
|                 byteBlock.Write(CRC16Utils.Crc16Only(byteBlock.Span)); | ||||
|                 ByteBlockExtension.WriteBackValue(ref byteBlock, (byte)(byteBlock.Span[1] + 128), 1); | ||||
|                 ByteBlockExtension.WriteBackValue(ref byteBlock, (byte)(byteBlock.Span[1] + 128), EndianType.Big, 1); | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 byteBlock.Write(bytes.Slice(0, 8).Span); | ||||
|                 ByteBlockExtension.Write(ref byteBlock, bytes.Slice(0, 8)); | ||||
|                 WriterExtension.WriteValue(ref byteBlock, (byte)1); | ||||
|                 ByteBlockExtension.WriteBackValue(ref byteBlock, (byte)(byteBlock.Length - 6), 5); | ||||
|                 ByteBlockExtension.WriteBackValue(ref byteBlock, (byte)(byteBlock.Span[7] + 128), 7); | ||||
|                 ByteBlockExtension.WriteBackValue(ref byteBlock, (byte)(byteBlock.Length - 6), EndianType.Big, 5); | ||||
|                 ByteBlockExtension.WriteBackValue(ref byteBlock, (byte)(byteBlock.Span[7] + 128), EndianType.Big, 7); | ||||
|             } | ||||
|             await ReturnData(client, byteBlock.Memory, e).ConfigureAwait(false); | ||||
|         } | ||||
| @@ -654,20 +658,20 @@ public class ModbusSlave : DeviceBase, IModbusAddress | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private async Task WriteSuccess(bool modbusRtu, IClientChannel client, ReadOnlyMemory<byte> bytes, ReceivedDataEventArgs e) | ||||
|     private async Task WriteSuccess(bool modbusRtu, IClientChannel client, ReadOnlySequence<byte> bytes, ReceivedDataEventArgs e) | ||||
|     { | ||||
|         ValueByteBlock byteBlock = new(20); | ||||
|         try | ||||
|         { | ||||
|             if (modbusRtu) | ||||
|             { | ||||
|                 byteBlock.Write(bytes.Slice(0, 6).Span); | ||||
|                 ByteBlockExtension.Write(ref byteBlock, bytes.Slice(0, 6)); | ||||
|                 byteBlock.Write(CRC16Utils.Crc16Only(byteBlock.Span)); | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 byteBlock.Write(bytes.Slice(0, 12).Span); | ||||
|                 ByteBlockExtension.WriteBackValue(ref byteBlock, (byte)(byteBlock.Length - 6), 5); | ||||
|                 ByteBlockExtension.Write(ref byteBlock, bytes.Slice(0, 12)); | ||||
|                 ByteBlockExtension.WriteBackValue(ref byteBlock, (byte)(byteBlock.Length - 6), EndianType.Big, 5); | ||||
|             } | ||||
|             await ReturnData(client, byteBlock.Memory, e).ConfigureAwait(false); | ||||
|         } | ||||
|   | ||||
| @@ -423,12 +423,14 @@ public class OpcUaMaster : IDisposable, IAsyncDisposable | ||||
|         Disconnect(); | ||||
|         _variableDicts?.Clear(); | ||||
|         _subscriptionDicts?.Clear(); | ||||
|         waitLock?.Dispose(); | ||||
|     } | ||||
|     public async ValueTask DisposeAsync() | ||||
|     { | ||||
|         await DisconnectAsync().ConfigureAwait(false); | ||||
|         _variableDicts?.Clear(); | ||||
|         _subscriptionDicts?.Clear(); | ||||
|         waitLock?.Dispose(); | ||||
|     } | ||||
|     /// <summary> | ||||
|     /// 获取变量说明 | ||||
|   | ||||
| @@ -1932,7 +1932,7 @@ | ||||
| //    } | ||||
| //    private void WriteDiagnosticInfo(string fieldName, DiagnosticInfo value, int depth) | ||||
| //    { | ||||
| //        if (value == null || value.IsNullDiagnosticInfo) | ||||
| //        if (value?.IsNullDiagnosticInfo != false) | ||||
| //        { | ||||
| //            WriteSimpleField(fieldName, null, quotes: false); | ||||
| //            return; | ||||
|   | ||||
| @@ -8,6 +8,8 @@ | ||||
| //  QQ群:605534569 | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
| using System.Buffers; | ||||
|  | ||||
| using TouchSocket.Core; | ||||
|  | ||||
| namespace ThingsGateway.Foundation.SiemensS7; | ||||
| @@ -23,108 +25,116 @@ public class S7Message : MessageBase, IResultMessage | ||||
|     public byte? Error { get; set; } | ||||
|  | ||||
|     /// <inheritdoc/> | ||||
|     public override int HeaderLength => 4; | ||||
|     public override long HeaderLength => 4; | ||||
|  | ||||
|     public override bool CheckHead<TByteBlock>(ref TByteBlock byteBlock) | ||||
|     { | ||||
|         byteBlock.Position += 2; | ||||
|         byteBlock.BytesRead += 2; | ||||
|         BodyLength = ReaderExtension.ReadValue<TByteBlock, ushort>(ref byteBlock, EndianType.Big) - 4; | ||||
|         return true; | ||||
|     } | ||||
|     public override FilterResult CheckBody<TByteBlock>(ref TByteBlock byteBlock) | ||||
|     { | ||||
|         var pos = byteBlock.Position; | ||||
|         var span = byteBlock.Span; | ||||
|         if (span[pos + 1] == 0xD0) // 首次握手0XD0连接确认 | ||||
|         var pos = byteBlock.BytesRead; | ||||
|         var sequence = byteBlock.TotalSequence; | ||||
|         if (sequence.GetByte(pos + 1) == 0xD0) // 首次握手0XD0连接确认 | ||||
|         { | ||||
|             OperCode = 0; | ||||
|             return FilterResult.Success; | ||||
|         } | ||||
|         else if (span[pos + 15] == 0xF0) // PDU | ||||
|         else if (sequence.GetByte(pos + 15) == 0xF0) // PDU | ||||
|         { | ||||
|             // 其余情况判断错误代码 | ||||
|             if (span[pos + 13] + span[pos + 14] > 0) // 如果错误代码不为0 | ||||
|             if (sequence.GetByte(pos + 13) + sequence.GetByte(pos + 14) > 0) // 如果错误代码不为0 | ||||
|             { | ||||
|                 OperCode = 999; | ||||
|                 ErrorMessage = string.Format(AppResource.ReturnError, span[pos + 13].ToString("X2"), span[pos + 14].ToString("X2")); | ||||
|                 ErrorMessage = string.Format(AppResource.ReturnError, sequence.GetByte(pos + 13).ToString("X2"), sequence.GetByte(pos + 14).ToString("X2")); | ||||
|                 return FilterResult.Success; | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 Content = byteBlock.ToArray(byteBlock.Length - 2, 2); | ||||
|                 Content = byteBlock.TotalSequence.Slice(byteBlock.BytesRead + byteBlock.BytesRemaining - 2, 2).ToArray(); | ||||
|                 OperCode = 0; | ||||
|                 return FilterResult.Success; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         //分bit/byte解析 | ||||
|         else if (span[pos + 15] == 0x04) // Read | ||||
|         else if (sequence.GetByte(pos + 15) == 0x04) // Read | ||||
|         { | ||||
|             byteBlock.Position = pos + 7; | ||||
|             byteBlock.BytesRead = pos + 7; | ||||
|             var sign = ReaderExtension.ReadValue<TByteBlock, ushort>(ref byteBlock, EndianType.Big);//数据ID标识 | ||||
|             Sign = sign; | ||||
|             byteBlock.Position = pos; | ||||
|             byteBlock.BytesRead = pos; | ||||
|  | ||||
|             int length = span[pos + 17]; | ||||
|             int itemLen = span[pos + 16]; | ||||
|             int length = sequence.GetByte(pos + 17); | ||||
|             int itemLen = sequence.GetByte(pos + 16); | ||||
|  | ||||
|             //添加错误代码校验 | ||||
|             // 其余情况判断错误代码 | ||||
|             if (span[pos + 13] + span[pos + 14] > 0) // 如果错误代码不为0 | ||||
|             if (sequence.GetByte(pos + 13) + sequence.GetByte(pos + 14) > 0) // 如果错误代码不为0 | ||||
|             { | ||||
|                 OperCode = 999; | ||||
|                 ErrorMessage = string.Format(AppResource.ReturnError, span[pos + 13].ToString("X2"), span[pos + 14].ToString("X2")); | ||||
|                 ErrorMessage = string.Format(AppResource.ReturnError, sequence.GetByte(pos + 13).ToString("X2"), sequence.GetByte(pos + 14).ToString("X2")); | ||||
|                 return FilterResult.Success; | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 if (byteBlock.Length < pos + 18) | ||||
|                 if (byteBlock.BytesRead + byteBlock.BytesRemaining < pos + 18) | ||||
|                 { | ||||
|                     OperCode = 999; | ||||
|                     ErrorMessage = AppResource.DataLengthError; | ||||
|                     return FilterResult.Success; | ||||
|                 } | ||||
|                 if (span[pos + 17] != byte.MaxValue) | ||||
|                 if (sequence.GetByte(pos + 17) != byte.MaxValue) | ||||
|                 { | ||||
|                     OperCode = 999; | ||||
|                     ErrorMessage = string.Format(AppResource.ValidateDataError, span[pos + 17], SiemensHelper.GetCpuError(span[pos + 17])); | ||||
|                     ErrorMessage = string.Format(AppResource.ValidateDataError, sequence.GetByte(pos + 17), SiemensHelper.GetCpuError(sequence.GetByte(pos + 17))); | ||||
|                     return FilterResult.Success; | ||||
|                 } | ||||
|  | ||||
|                 using ValueByteBlock data = new(length); | ||||
|                 ValueByteBlock data = new(length); | ||||
|                 var dataIndex = pos + 17; | ||||
|                 for (int index = 0; index < itemLen; index++) | ||||
|                 { | ||||
|                     if (span[dataIndex] != byte.MaxValue) | ||||
|                     if (sequence.GetByte(dataIndex) != byte.MaxValue) | ||||
|                     { | ||||
|                         OperCode = 999; | ||||
|                         ErrorMessage = string.Format(AppResource.ValidateDataError, span[dataIndex], SiemensHelper.GetCpuError(span[dataIndex])); | ||||
|                         ErrorMessage = string.Format(AppResource.ValidateDataError, sequence.GetByte(dataIndex), SiemensHelper.GetCpuError(sequence.GetByte(dataIndex))); | ||||
|                         return FilterResult.Success; | ||||
|                     } | ||||
|  | ||||
|                     if (span[dataIndex + 1] == 4)//Bit:3;Byte:4;Counter或者Timer:9 | ||||
|                     if (sequence.GetByte(dataIndex + 1) == 4)//Bit:3;Byte:4;Counter或者Timer:9 | ||||
|                     { | ||||
|                         byteBlock.Position = dataIndex + 2; | ||||
|                         byteBlock.BytesRead = dataIndex + 2; | ||||
|                         var byteLength = ReaderExtension.ReadValue<TByteBlock, ushort>(ref byteBlock, EndianType.Big) / 8; | ||||
|                         data.Write(span.Slice(dataIndex + 4, byteLength)); | ||||
|                         ByteBlockExtension.Write(ref data, sequence.Slice(dataIndex + 4, byteLength)); | ||||
|                         dataIndex += byteLength + 4; | ||||
|                     } | ||||
|                     else if (span[dataIndex + 1] == 9)//Counter或者Timer:9 | ||||
|                     else if (sequence.GetByte(dataIndex + 1) == 9)//Counter或者Timer:9 | ||||
|                     { | ||||
|                         byteBlock.Position = dataIndex + 2; | ||||
|                         byteBlock.BytesRead = dataIndex + 2; | ||||
|                         var byteLength = ReaderExtension.ReadValue<TByteBlock, ushort>(ref byteBlock, EndianType.Big); | ||||
|                         if (byteLength % 3 == 0) | ||||
|                         { | ||||
|                             for (int indexCT = 0; indexCT < byteLength / 3; indexCT++) | ||||
|                             { | ||||
|                                 data.Write(byteBlock.Span.Slice(dataIndex + 5 + (3 * indexCT), 2)); | ||||
|                                 var readOnlyMemories = byteBlock.TotalSequence.Slice(dataIndex + 5 + (3 * indexCT), 2); | ||||
|                                 foreach (var item in readOnlyMemories) | ||||
|                                 { | ||||
|                                     data.Write(item.Span); | ||||
|                                 } | ||||
|                             } | ||||
|                         } | ||||
|                         else | ||||
|                         { | ||||
|                             for (int indexCT = 0; indexCT < byteLength / 5; indexCT++) | ||||
|                             { | ||||
|                                 data.Write(byteBlock.Span.Slice(dataIndex + 7 + (5 * indexCT), 2)); | ||||
|                                 var readOnlyMemories = byteBlock.TotalSequence.Slice(dataIndex + 7 + (5 * indexCT), 2); | ||||
|                                 foreach (var item in readOnlyMemories) | ||||
|                                 { | ||||
|                                     data.Write(item.Span); | ||||
|                                 } | ||||
|                             } | ||||
|                         } | ||||
|                         dataIndex += byteLength + 4; | ||||
| @@ -133,23 +143,24 @@ public class S7Message : MessageBase, IResultMessage | ||||
|  | ||||
|                 OperCode = 0; | ||||
|                 Content = data.ToArray(); | ||||
|                 data.SafeDispose(); | ||||
|                 return FilterResult.Success; | ||||
|             } | ||||
|         } | ||||
|         else if (span[pos + 15] == 0x05) // Write | ||||
|         else if (sequence.GetByte(pos + 15) == 0x05) // Write | ||||
|         { | ||||
|             byteBlock.Position = pos + 7; | ||||
|             byteBlock.BytesRead = pos + 7; | ||||
|             var sign = ReaderExtension.ReadValue<TByteBlock, ushort>(ref byteBlock, EndianType.Big);//数据ID标识 | ||||
|             Sign = sign; | ||||
|             byteBlock.Position = pos; | ||||
|             int itemLen = span[pos + 16]; | ||||
|             if (span[pos + 13] + span[pos + 14] > 0) // 如果错误代码不为0 | ||||
|             byteBlock.BytesRead = pos; | ||||
|             int itemLen = sequence.GetByte(pos + 16); | ||||
|             if (sequence.GetByte(pos + 13) + sequence.GetByte(pos + 14) > 0) // 如果错误代码不为0 | ||||
|             { | ||||
|                 OperCode = 999; | ||||
|                 ErrorMessage = string.Format(AppResource.ReturnError, span[pos + 13].ToString("X2"), span[pos + 14].ToString("X2")); | ||||
|                 ErrorMessage = string.Format(AppResource.ReturnError, sequence.GetByte(pos + 13).ToString("X2"), sequence.GetByte(pos + 14).ToString("X2")); | ||||
|                 return FilterResult.Success; | ||||
|             } | ||||
|             if (byteBlock.Length < pos + 18) | ||||
|             if (byteBlock.BytesRead + byteBlock.BytesRemaining < pos + 18) | ||||
|             { | ||||
|                 OperCode = 999; | ||||
|                 ErrorMessage = AppResource.DataLengthError; | ||||
| @@ -157,10 +168,10 @@ public class S7Message : MessageBase, IResultMessage | ||||
|             } | ||||
|             for (int i = 0; i < itemLen; i++) | ||||
|             { | ||||
|                 if (span[pos + 17 + i] != byte.MaxValue) | ||||
|                 if (sequence.GetByte(pos + 17 + i) != byte.MaxValue) | ||||
|                 { | ||||
|                     OperCode = 999; | ||||
|                     ErrorMessage = string.Format(AppResource.ValidateDataError, span[pos + 17 + i], SiemensHelper.GetCpuError(span[pos + 17 + i])); | ||||
|                     ErrorMessage = string.Format(AppResource.ValidateDataError, sequence.GetByte(pos + 17 + i), SiemensHelper.GetCpuError(sequence.GetByte(pos + 17 + i))); | ||||
|                     return FilterResult.Success; | ||||
|                 } | ||||
|             } | ||||
|   | ||||
| @@ -78,7 +78,7 @@ public class S7Send : ISendMessage | ||||
|     public int MaxLength => 2048; | ||||
|     public int Sign { get; set; } | ||||
|  | ||||
|     public void Build<TByteBlock>(ref TByteBlock byteBlock) where TByteBlock : IByteBlockWriter | ||||
|     public void Build<TByteBlock>(ref TByteBlock byteBlock) where TByteBlock : IBytesWriter | ||||
|     { | ||||
|         if (Handshake == true) | ||||
|         { | ||||
| @@ -95,7 +95,7 @@ public class S7Send : ISendMessage | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     internal void GetReadCommand<TByteBlock>(ref TByteBlock byteBlock, SiemensS7Address[] siemensS7Address) where TByteBlock : IByteBlockWriter | ||||
|     internal void GetReadCommand<TByteBlock>(ref TByteBlock byteBlock, SiemensS7Address[] siemensS7Address) where TByteBlock : IBytesWriter | ||||
|     { | ||||
|         byte len = (byte)siemensS7Address.Length; | ||||
|         ushort telegramLen = (ushort)(len * 12 + 19); | ||||
| @@ -142,7 +142,7 @@ public class S7Send : ISendMessage | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     internal void GetWriteByteCommand<TByteBlock>(ref TByteBlock byteBlock, SiemensS7Address[] addresss) where TByteBlock : IByteBlockWriter | ||||
|     internal void GetWriteByteCommand<TByteBlock>(ref TByteBlock byteBlock, SiemensS7Address[] addresss) where TByteBlock : IBytesWriter | ||||
|     { | ||||
|         byte itemLen = (byte)addresss.Length; | ||||
|         ushort parameterLen = (ushort)(itemLen * 12 + 2); | ||||
| @@ -200,10 +200,8 @@ public class S7Send : ISendMessage | ||||
|             dataLen = (ushort)(dataLen + data.Length + 4); | ||||
|         } | ||||
|         ushort telegramLen = (ushort)(itemLen * 12 + 19 + dataLen); | ||||
|         byteBlock.Position = 2; | ||||
|         WriterExtension.WriteValue(ref byteBlock, (ushort)telegramLen, EndianType.Big);//长度 | ||||
|         byteBlock.Position = 15; | ||||
|         WriterExtension.WriteValue(ref byteBlock, (ushort)dataLen, EndianType.Big);//长度 | ||||
|         byteBlock.Position = byteBlock.Length; | ||||
|  | ||||
|         ByteBlockExtension.WriteBackValue(ref byteBlock, (ushort)telegramLen, EndianType.Big, 2);//长度 | ||||
|         ByteBlockExtension.WriteBackValue(ref byteBlock, (ushort)dataLen, EndianType.Big, 15);//长度 | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -15,10 +15,19 @@ using ThingsGateway.Foundation.Extension.String; | ||||
|  | ||||
| using TouchSocket.Core; | ||||
|  | ||||
| using Xunit.Abstractions; | ||||
|  | ||||
| namespace ThingsGateway.Foundation.Test; | ||||
|  | ||||
| public class Dlt645Test | ||||
| { | ||||
|     public Dlt645Test(ITestOutputHelper output) | ||||
|     { | ||||
|         _output = output; | ||||
|  | ||||
|     } | ||||
|     private readonly ITestOutputHelper _output; | ||||
|  | ||||
|     [Theory] | ||||
|     [InlineData("02010100", "FE FE FE FE 68 11 11 11 11 11 11 68 91 07 33 34 34 35 33 59 36 60 16 ")] | ||||
|     public async Task Dlt645_Read_OK(string address, string data) | ||||
| @@ -31,10 +40,11 @@ public class Dlt645Test | ||||
|         { | ||||
|             a.AddEasyLogger((a, b, c, d) => | ||||
|             { | ||||
|                 Debug.WriteLine($"{c}{Environment.NewLine}{d?.ToString()}"); | ||||
|                 _output.WriteLine($"{c}{Environment.NewLine}{d?.ToString()}"); | ||||
|             }, LogLevel.Trace); | ||||
|         }); | ||||
|         var dltMaster = new Dlt645_2007Master() { Timeout = 10000, Station = "111111111111" }; | ||||
|  | ||||
|         var dltMaster = new Dlt645_2007Master() { Timeout = 30000, Station = "111111111111" }; | ||||
|         dltMaster.InitChannel(dltChannel); | ||||
|         await dltChannel.SetupAsync(dltChannel.Config); | ||||
|         await dltMaster.ConnectAsync(CancellationToken.None); | ||||
| @@ -42,19 +52,20 @@ public class Dlt645Test | ||||
|  | ||||
|         var task1 = Task.Run(async () => | ||||
|          { | ||||
|              Stopwatch stopwatch = new Stopwatch(); | ||||
|              stopwatch.Start(); | ||||
|              var result = await dltMaster.ReadAsync(address, default).ConfigureAwait(false); | ||||
|              stopwatch.Stop(); | ||||
|              Assert.True(result.IsSuccess, result.ToString()); | ||||
|          }); | ||||
|         await Task.Delay(50); | ||||
|         var task2 = Task.Run(async () => | ||||
|         { | ||||
|             var bytes = data.HexStringToBytes().GetArray(); | ||||
|             foreach (var item in bytes) | ||||
|             { | ||||
|                 var data = new ByteBlock(1); data.WriteByte(item); | ||||
|                 await adapter.ReceivedInputAsync(data).ConfigureAwait(false); | ||||
|             } | ||||
|             SingleStreamDataHandlingAdapterTest singleStreamDataHandlingAdapterTest = new(); | ||||
|             await singleStreamDataHandlingAdapterTest.SendCallback(data.HexStringToBytes(), (a) => singleStreamDataHandlingAdapterTest.ReceivedAsync(adapter, CancellationToken.None), 1, CancellationToken.None).ConfigureAwait(false); | ||||
|         }); | ||||
|  | ||||
|         await Task.WhenAll(task1, task2); | ||||
|     } | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -8,24 +8,34 @@ | ||||
| // QQ群:605534569 | ||||
| // ------------------------------------------------------------------------------ | ||||
|  | ||||
| using System.Diagnostics; | ||||
|  | ||||
| using ThingsGateway.Foundation.Extension.String; | ||||
| using ThingsGateway.Foundation.Modbus; | ||||
|  | ||||
| using TouchSocket.Core; | ||||
|  | ||||
| using Xunit.Abstractions; | ||||
|  | ||||
| namespace ThingsGateway.Foundation.Test; | ||||
|  | ||||
| public class ModbusTest | ||||
| { | ||||
|     public ModbusTest(ITestOutputHelper output) | ||||
|     { | ||||
|         _output = output; | ||||
|  | ||||
|     } | ||||
|     private readonly ITestOutputHelper _output; | ||||
|  | ||||
|  | ||||
|     [Theory] | ||||
|     [InlineData("400045", true, "00000000002F01032C0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000")] | ||||
|     [InlineData("300045", true, "00000000002F01042C0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000")] | ||||
|     [InlineData("100045", true, "000000000009010206000000000000")] | ||||
|     [InlineData("000045", true, "000000000009010106000000000000")] | ||||
|     [InlineData("400045", false, "0000000000060106002C0001", "1", DataTypeEnum.UInt16)] | ||||
|     [InlineData("000045", false, "0000000000060105002CFF00", "true", DataTypeEnum.Boolean)] | ||||
|     [InlineData("400045", true, "00010000002F01032C0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000")] | ||||
|     [InlineData("300045", true, "00010000002F01042C0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000")] | ||||
|     [InlineData("100045", true, "000100000009010206000000000000")] | ||||
|     [InlineData("000045", true, "000100000009010106000000000000")] | ||||
|     [InlineData("400045", false, "0001000000060106002C0001", "1", DataTypeEnum.UInt16)] | ||||
|     [InlineData("000045", false, "0001000000060105002CFF00", "true", DataTypeEnum.Boolean)] | ||||
|     [InlineData("400045;w=16", false, "0001000000090110002C0001020001", "1", DataTypeEnum.UInt16)] | ||||
|     [InlineData("000045;w=15", false, "000100000008010F002C00010101", "true", DataTypeEnum.Boolean)] | ||||
|     public async Task ModbusTcp_ReadWrite_OK(string address, bool read, string data, string writeData = null, DataTypeEnum dataTypeEnum = DataTypeEnum.UInt16) | ||||
|     { | ||||
|         var modbusChannel = new TouchSocketConfig().GetChannel(new ChannelOptions() | ||||
| @@ -36,7 +46,7 @@ public class ModbusTest | ||||
|         { | ||||
|             a.AddEasyLogger((a, b, c, d) => | ||||
|             { | ||||
|                 Debug.WriteLine($"{c}{Environment.NewLine}{d?.ToString()}"); | ||||
|                 _output.WriteLine($"{c}{Environment.NewLine}{d?.ToString()}"); | ||||
|             }, LogLevel.Trace); | ||||
|         }); | ||||
|         var modbusMaster = new ModbusMaster() { ModbusType = ModbusTypeEnum.ModbusTcp, Timeout = 10000 }; | ||||
| @@ -61,12 +71,8 @@ public class ModbusTest | ||||
|         await Task.Delay(50); | ||||
|         var task2 = Task.Run(async () => | ||||
|         { | ||||
|             var bytes = data.HexStringToBytes().GetArray(); | ||||
|             foreach (var item in bytes) | ||||
|             { | ||||
|                 var data = new ByteBlock(1); data.WriteByte(item); | ||||
|                 await adapter.ReceivedInputAsync(data).ConfigureAwait(false); | ||||
|             } | ||||
|             SingleStreamDataHandlingAdapterTest singleStreamDataHandlingAdapterTest = new(); | ||||
|             await singleStreamDataHandlingAdapterTest.SendCallback(data.HexStringToBytes(), (a) => singleStreamDataHandlingAdapterTest.ReceivedAsync(adapter, CancellationToken.None), 1, CancellationToken.None).ConfigureAwait(false); | ||||
|         }); | ||||
|         await Task.WhenAll(task1, task2); | ||||
|     } | ||||
| @@ -88,7 +94,7 @@ public class ModbusTest | ||||
|         { | ||||
|             a.AddEasyLogger((a, b, c, d) => | ||||
|            { | ||||
|                Debug.WriteLine($"{c}{Environment.NewLine}{d?.ToString()}"); | ||||
|                _output.WriteLine($"{c}{Environment.NewLine}{d?.ToString()}"); | ||||
|            }, LogLevel.Trace); | ||||
|         }); | ||||
|         var modbusMaster = new ModbusMaster() { ModbusType = ModbusTypeEnum.ModbusRtu, Timeout = 10000, Station = 1 }; | ||||
| @@ -113,12 +119,8 @@ public class ModbusTest | ||||
|         await Task.Delay(50); | ||||
|         var task2 = Task.Run(async () => | ||||
|         { | ||||
|             var bytes = data.HexStringToBytes().GetArray(); | ||||
|             foreach (var item in bytes) | ||||
|             { | ||||
|                 var data = new ByteBlock(1); data.WriteByte(item); | ||||
|                 await (adapter).ReceivedInputAsync(data).ConfigureAwait(false); | ||||
|             } | ||||
|             SingleStreamDataHandlingAdapterTest singleStreamDataHandlingAdapterTest = new(); | ||||
|             await singleStreamDataHandlingAdapterTest.SendCallback(data.HexStringToBytes(), (a) => singleStreamDataHandlingAdapterTest.ReceivedAsync(adapter, CancellationToken.None), 1, CancellationToken.None).ConfigureAwait(false); | ||||
|         }); | ||||
|         await Task.WhenAll(task1, task2); | ||||
|     } | ||||
|   | ||||
| @@ -8,22 +8,29 @@ | ||||
| // QQ群:605534569 | ||||
| // ------------------------------------------------------------------------------ | ||||
|  | ||||
| using System.Diagnostics; | ||||
|  | ||||
| using ThingsGateway.Foundation.Extension.String; | ||||
| using ThingsGateway.Foundation.SiemensS7; | ||||
| using ThingsGateway.NewLife.Extension; | ||||
| using ThingsGateway.NewLife.Reflection; | ||||
|  | ||||
| using TouchSocket.Core; | ||||
|  | ||||
| using Xunit.Abstractions; | ||||
|  | ||||
| namespace ThingsGateway.Foundation.Test; | ||||
|  | ||||
| public class SiemensS7Test | ||||
| { | ||||
|  | ||||
|     public SiemensS7Test(ITestOutputHelper output) | ||||
|     { | ||||
|         _output = output; | ||||
|  | ||||
|     } | ||||
|     private readonly ITestOutputHelper _output; | ||||
|  | ||||
|  | ||||
|     [Theory] | ||||
|     [InlineData("M100", true, "03 00 00 1B 02 F0 80 32 03 00 00 00 03 00 02 00 06 00 00 04 01 FF 04 00 10 00 00")] | ||||
|     [InlineData("M100", false, "03 00 00 16 02 F0 80 32 03 00 00 00 03 00 02 00 01 00 00 05 01 FF", "1", DataTypeEnum.UInt16)] | ||||
|     [InlineData("M100", true, "03 00 00 1B 02 F0 80 32 03 00 00 00 01 00 02 00 06 00 00 04 01 FF 04 00 10 00 00")] | ||||
|     [InlineData("M100", false, "03 00 00 16 02 F0 80 32 03 00 00 00 01 00 02 00 01 00 00 05 01 FF", "1", DataTypeEnum.UInt16)] | ||||
|     public async Task SiemensS7_ReadWrite_OK(string address, bool read, string data, string writeData = null, DataTypeEnum dataTypeEnum = DataTypeEnum.UInt16) | ||||
|     { | ||||
|         var siemensS7Channel = new TouchSocketConfig().GetChannel(new ChannelOptions() | ||||
| @@ -34,7 +41,7 @@ public class SiemensS7Test | ||||
|         { | ||||
|             a.AddEasyLogger((a, b, c, d) => | ||||
|             { | ||||
|                 Debug.WriteLine($"{c}{Environment.NewLine}{d?.ToString()}"); | ||||
|                 _output.WriteLine($"{c}{Environment.NewLine}{d?.ToString()}"); | ||||
|             }, LogLevel.Trace); | ||||
|         }); | ||||
|  | ||||
| @@ -57,16 +64,12 @@ public class SiemensS7Test | ||||
|                 Assert.True(result.IsSuccess, result.ToString()); | ||||
|             } | ||||
|         }); | ||||
|         await Task.Delay(500); | ||||
|  | ||||
|         var task2 = Task.Run(async () => | ||||
|         { | ||||
|             await Task.Delay(1000).ConfigureAwait(false); | ||||
|             var bytes = data.HexStringToBytes().GetArray(); | ||||
|             bytes[12] = (byte)(((IClientChannel)(siemensS7Master.Channel)).WaitHandlePool.GetValue("m_currentSign").ToInt() - 1); | ||||
|             foreach (var item in bytes) | ||||
|             { | ||||
|                 var data = new ByteBlock(1); data.WriteByte(item); | ||||
|                 await adapter.ReceivedInputAsync(data).ConfigureAwait(false); | ||||
|             } | ||||
|             SingleStreamDataHandlingAdapterTest singleStreamDataHandlingAdapterTest = new(); | ||||
|             await singleStreamDataHandlingAdapterTest.SendCallback(data.HexStringToBytes(), (a) => singleStreamDataHandlingAdapterTest.ReceivedAsync(adapter, CancellationToken.None), 1, CancellationToken.None).ConfigureAwait(false); | ||||
|         }); | ||||
|         await Task.WhenAll(task1, task2); | ||||
|     } | ||||
|   | ||||
| @@ -0,0 +1,72 @@ | ||||
| // ------------------------------------------------------------------------------ | ||||
| // 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||
| // 此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||
| // 源代码使用协议遵循本仓库的开源协议及附加协议 | ||||
| // Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||
| // Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||
| // 使用文档:https://thingsgateway.cn/ | ||||
| // QQ群:605534569 | ||||
| // ------------------------------------------------------------------------------ | ||||
|  | ||||
| using System.IO.Pipelines; | ||||
|  | ||||
| using TouchSocket.Core; | ||||
|  | ||||
| namespace ThingsGateway.Foundation.Test; | ||||
|  | ||||
| public class SingleStreamDataHandlingAdapterTest | ||||
| { | ||||
|     private readonly Pipe m_pipe = new Pipe(new PipeOptions(new SmallBlockMemoryPool(1), default, default, 1024 * 1024 * 1024, 1024 * 1024 * 512, -1)); | ||||
|     public async Task SendCallback(ReadOnlyMemory<byte> memory, Func<int, Task> func, int bufferLength = 1, CancellationToken token = default) | ||||
|     { | ||||
|         try | ||||
|         { | ||||
|             var offset = 0; | ||||
|             while (true) | ||||
|             { | ||||
|                 token.ThrowIfCancellationRequested(); | ||||
|  | ||||
|                 var remainingLength = memory.Length - offset; | ||||
|                 if (remainingLength <= 0) | ||||
|                 { | ||||
|                     break; | ||||
|                 } | ||||
|                 var sliceMemory = memory.Slice(offset, Math.Min(remainingLength, bufferLength)); | ||||
|                 await this.m_pipe.Writer.WriteAsync(sliceMemory, token).ConfigureAwait(EasyTask.ContinueOnCapturedContext); | ||||
|                 offset += sliceMemory.Length; | ||||
|  | ||||
|                 //await this.m_pipe.Writer.FlushAsync(token).ConfigureAwait(EasyTask.ContinueOnCapturedContext); | ||||
|  | ||||
|                 await func(offset).ConfigureAwait(EasyTask.ContinueOnCapturedContext); | ||||
|             } | ||||
|         } | ||||
|         finally | ||||
|         { | ||||
|             await this.m_pipe.Writer.CompleteAsync().ConfigureAwait(false); | ||||
|             await this.m_pipe.Reader.CompleteAsync().ConfigureAwait(false); | ||||
|         } | ||||
|     } | ||||
|     public async Task ReceivedAsync(SingleStreamDataHandlingAdapter adapter, CancellationToken token) | ||||
|     { | ||||
|         try | ||||
|         { | ||||
|             var readResult = await this.m_pipe.Reader.ReadAsync(token).ConfigureAwait(false); | ||||
|             var sequence = readResult.Buffer; | ||||
|             var reader = new ClassBytesReader(sequence); | ||||
|             await adapter.ReceivedInputAsync(reader).ConfigureAwait(EasyTask.ContinueOnCapturedContext); | ||||
|             var position = sequence.GetPosition(reader.BytesRead); | ||||
|             // 通知PipeReader已消费 | ||||
|             this.m_pipe.Reader.AdvanceTo(position, sequence.End); | ||||
|             // 如果本次ReadAsync已完成或被取消,直接返回 | ||||
|             if (readResult.IsCanceled || readResult.IsCompleted) | ||||
|             { | ||||
|                 return; | ||||
|             } | ||||
|  | ||||
|         } | ||||
|         finally | ||||
|         { | ||||
|  | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,43 @@ | ||||
| // ------------------------------------------------------------------------------ | ||||
| // 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||
| // 此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||
| // 源代码使用协议遵循本仓库的开源协议及附加协议 | ||||
| // Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||
| // Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||
| // 使用文档:https://thingsgateway.cn/ | ||||
| // QQ群:605534569 | ||||
| // ------------------------------------------------------------------------------ | ||||
|  | ||||
| namespace ThingsGateway.Foundation.Test; | ||||
|  | ||||
| using System; | ||||
| using System.Buffers; | ||||
|  | ||||
| public class SmallBlockMemoryPool : MemoryPool<byte> | ||||
| { | ||||
|     private readonly int _blockSize; | ||||
|  | ||||
|     public SmallBlockMemoryPool(int blockSize) | ||||
|     { | ||||
|         _blockSize = blockSize; | ||||
|     } | ||||
|  | ||||
|     public override int MaxBufferSize => _blockSize; | ||||
|  | ||||
|     public override IMemoryOwner<byte> Rent(int minBufferSize = -1) | ||||
|     { | ||||
|         if (minBufferSize <= 0) minBufferSize = _blockSize; | ||||
|         return new SmallBlockOwner(new byte[Math.Min(minBufferSize, _blockSize)]); | ||||
|     } | ||||
|  | ||||
|     protected override void Dispose(bool disposing) { } | ||||
|  | ||||
|     private class SmallBlockOwner : IMemoryOwner<byte> | ||||
|     { | ||||
|         private readonly byte[] _array; | ||||
|         public SmallBlockOwner(byte[] array) => _array = array; | ||||
|         public Memory<byte> Memory => _array; | ||||
|         public void Dispose() { } | ||||
|     } | ||||
| } | ||||
|  | ||||
| @@ -11,11 +11,31 @@ | ||||
| using ThingsGateway.Foundation; | ||||
|  | ||||
| using TouchSocket.Core; | ||||
| using TouchSocket.Sockets; | ||||
|  | ||||
| internal static class TestAdapterHelper | ||||
| { | ||||
|     public static void TestAdapter<T>(byte[] data, bool isSingleStreamData) where T : MessageBase, new() | ||||
|  | ||||
|     public static async Task ReceivedInputAsync(SingleStreamDataHandlingAdapter adapter, ReadOnlyMemory<byte> memory, CancellationToken token) | ||||
|     { | ||||
|         var offset = 0; | ||||
|         while (true) | ||||
|         { | ||||
|             token.ThrowIfCancellationRequested(); | ||||
|  | ||||
|             var remainingLength = memory.Length - offset; | ||||
|             if (remainingLength <= 0) | ||||
|             { | ||||
|                 break; | ||||
|             } | ||||
|             var sliceMemory = memory.Slice(offset, Math.Min(remainingLength, 1)); | ||||
|             var reader = new ClassBytesReader(sliceMemory); | ||||
|             await adapter.ReceivedInputAsync(reader).ConfigureAwait(false); | ||||
|             offset += sliceMemory.Length; | ||||
|         } | ||||
|  | ||||
|     } | ||||
|  | ||||
|     public static async Task TestAdapter<T>(byte[] data, bool isSingleStreamData) where T : MessageBase, new() | ||||
|     { | ||||
|         bool isSuccess = false; | ||||
|         MessageBase message = default; | ||||
| @@ -25,7 +45,7 @@ internal static class TestAdapterHelper | ||||
|             for (int bufferLength = 1; bufferLength < 256; bufferLength += 1) | ||||
|             { | ||||
|                 SingleStreamDataAdapterTester tester = SingleStreamDataAdapterTester.CreateTester(new DeviceSingleStreamDataHandleAdapter<T>() | ||||
|                     , bufferLength, (byteBlock, requestInfo) => | ||||
|                     , (byteBlock, requestInfo) => | ||||
|                     { | ||||
|                         //此处就是接收,如果是自定义适配器,可以将requestInfo强制转换为实际对象,然后判断数据的确定性 | ||||
|                         //if (byteBlock.Length != 15 || (!byteBlock.ToArray().SequenceEqual(data))) | ||||
| @@ -50,7 +70,8 @@ internal static class TestAdapterHelper | ||||
|  | ||||
|                 try | ||||
|                 { | ||||
|                     var time = tester.Run(data, 2, 2, 1000 * 10); | ||||
|                     using var cts = new CancellationTokenSource(1000 * 10); | ||||
|                     var time = await tester.RunAsync(data, 2, 2, bufferLength, cts.Token).ConfigureAwait(false); | ||||
|                 } | ||||
|                 catch (Exception ex) | ||||
|                 { | ||||
| @@ -78,7 +99,7 @@ internal static class TestAdapterHelper | ||||
|          }); | ||||
|             try | ||||
|             { | ||||
|                 var time = tester.Run(data, 2, 2, 1000 * 10); | ||||
|                 var time = await tester.RunAsync(data, 2, 2, 1000 * 10).ConfigureAwait(false); | ||||
|             } | ||||
|             catch (Exception ex) | ||||
|             { | ||||
|   | ||||
| @@ -1,35 +1,36 @@ | ||||
| <Project Sdk="Microsoft.NET.Sdk"> | ||||
|  | ||||
|   <PropertyGroup> | ||||
|     <TargetFrameworks>net9.0</TargetFrameworks> | ||||
|     <IsPackable>false</IsPackable> | ||||
|      | ||||
|   </PropertyGroup> | ||||
| 	<PropertyGroup> | ||||
| 		<TargetFrameworks>net9.0</TargetFrameworks> | ||||
| 		<IsPackable>false</IsPackable> | ||||
|  | ||||
|   <ItemGroup> | ||||
|     <PackageReference Include="coverlet.collector" Version="6.0.4"> | ||||
|       <PrivateAssets>all</PrivateAssets> | ||||
|       <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> | ||||
|     </PackageReference> | ||||
|     <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1" /> | ||||
|     <PackageReference Include="xunit" Version="2.9.3" /> | ||||
|     <PackageReference Include="xunit.runner.visualstudio" Version="3.1.3"> | ||||
|       <PrivateAssets>all</PrivateAssets> | ||||
|       <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> | ||||
|     </PackageReference> | ||||
|     <ProjectReference Include="..\ThingsGateway.Foundation.Dlt645\ThingsGateway.Foundation.Dlt645.csproj" /> | ||||
| 	</PropertyGroup> | ||||
|  | ||||
|  | ||||
| 	  <ProjectReference Include="..\ThingsGateway.Foundation.Modbus\ThingsGateway.Foundation.Modbus.csproj" /> | ||||
| 	<ItemGroup> | ||||
| 		<PackageReference Include="coverlet.collector" Version="6.0.4"> | ||||
| 			<PrivateAssets>all</PrivateAssets> | ||||
| 			<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> | ||||
| 		</PackageReference> | ||||
| 		<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1" /> | ||||
| 		<PackageReference Include="xunit" Version="2.9.3" /> | ||||
| 		<PackageReference Include="xunit.runner.visualstudio" Version="3.1.4"> | ||||
| 			<PrivateAssets>all</PrivateAssets> | ||||
| 			<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> | ||||
| 		</PackageReference> | ||||
| 		<ProjectReference Include="..\ThingsGateway.Foundation.Dlt645\ThingsGateway.Foundation.Dlt645.csproj" /> | ||||
| 		<ProjectReference Include="..\ThingsGateway.Foundation.Modbus\ThingsGateway.Foundation.Modbus.csproj" /> | ||||
|  | ||||
| 		<ProjectReference Include="..\ThingsGateway.Foundation.SiemensS7\ThingsGateway.Foundation.SiemensS7.csproj" /> | ||||
|  | ||||
|  | ||||
| 	  <ProjectReference Include="..\ThingsGateway.Foundation.SiemensS7\ThingsGateway.Foundation.SiemensS7.csproj" /> | ||||
|  | ||||
| 	   | ||||
|   </ItemGroup> | ||||
| 	</ItemGroup> | ||||
|  | ||||
|   <ItemGroup> | ||||
|     <Using Include="Xunit" /> | ||||
|   </ItemGroup> | ||||
|  | ||||
|  | ||||
| 	<ItemGroup> | ||||
| 		<Using Include="Xunit" /> | ||||
| 	</ItemGroup> | ||||
|  | ||||
| </Project> | ||||
|   | ||||
| @@ -0,0 +1,40 @@ | ||||
| // ------------------------------------------------------------------------------ | ||||
| // 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||
| // 此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||
| // 源代码使用协议遵循本仓库的开源协议及附加协议 | ||||
| // Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||
| // Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||
| // 使用文档:https://thingsgateway.cn/ | ||||
| // QQ群:605534569 | ||||
| // ------------------------------------------------------------------------------ | ||||
|  | ||||
| using System.Net; | ||||
|  | ||||
| using TouchSocket.Core; | ||||
|  | ||||
| namespace ThingsGateway.Foundation.Test; | ||||
|  | ||||
| public class UdpDataHandlingAdapterTest | ||||
| { | ||||
|     public async Task SendCallback(UdpDataHandlingAdapter adapter, ReadOnlyMemory<byte> memory, CancellationToken token = default) | ||||
|     { | ||||
|         try | ||||
|         { | ||||
|             await ReceivedAsync(IPEndPoint.Parse("127.0.0.1:502"), adapter, memory, token).ConfigureAwait(EasyTask.ContinueOnCapturedContext); | ||||
|         } | ||||
|         finally | ||||
|         { | ||||
|         } | ||||
|     } | ||||
|     public async Task ReceivedAsync(IPEndPoint iPEndPoint, UdpDataHandlingAdapter adapter, ReadOnlyMemory<byte> memory, CancellationToken token) | ||||
|     { | ||||
|         try | ||||
|         { | ||||
|             await adapter.ReceivedInputAsync(iPEndPoint, memory).ConfigureAwait(EasyTask.ContinueOnCapturedContext); | ||||
|         } | ||||
|         finally | ||||
|         { | ||||
|  | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -27,19 +27,19 @@ | ||||
| 	<Target Name="IncludeAllFilesInTargetDir" AfterTargets="Build"> | ||||
|  | ||||
| 		<ItemGroup> | ||||
| 			<Content Include="$(ProjectDir)bin\$(Configuration)\$(TargetFramework)\**\*Plugin.DB*.dll"> | ||||
| 			<Content Include="$(ProjectDir)$(OutputPath)\$(TargetFramework)\**\*Plugin.DB*.dll"> | ||||
| 				<Pack>true</Pack> | ||||
| 				<PackagePath>Content</PackagePath> | ||||
| 			</Content> | ||||
| 			<Content Include="$(ProjectDir)bin\$(Configuration)\$(TargetFramework)\**\*TDengine*.dll"> | ||||
| 			<Content Include="$(ProjectDir)$(OutputPath)\$(TargetFramework)\**\*TDengine*.dll"> | ||||
| 				<Pack>true</Pack> | ||||
| 				<PackagePath>Content</PackagePath> | ||||
| 			</Content> | ||||
| 			<Content Include="$(ProjectDir)bin\$(Configuration)\$(TargetFramework)\**\*QuestDb*.dll"> | ||||
| 			<Content Include="$(ProjectDir)$(OutputPath)\$(TargetFramework)\**\*QuestDb*.dll"> | ||||
| 				<Pack>true</Pack> | ||||
| 				<PackagePath>Content</PackagePath> | ||||
| 			</Content> | ||||
| 			<Content Include="$(ProjectDir)bin\$(Configuration)\$(TargetFramework)\**\*CsvHelper*.dll"> | ||||
| 			<Content Include="$(ProjectDir)$(OutputPath)\$(TargetFramework)\**\*CsvHelper*.dll"> | ||||
| 				<Pack>true</Pack> | ||||
| 				<PackagePath>Content</PackagePath> | ||||
| 			</Content> | ||||
|   | ||||
| @@ -3,8 +3,10 @@ | ||||
| 	<Import Project="..\..\PackNuget.props" /> | ||||
| 	<PropertyGroup> | ||||
| 		<TargetFrameworks>net8.0;</TargetFrameworks> | ||||
|   | ||||
| 		 | ||||
|  | ||||
| 		<OutputPath>bin\$(Configuration)</OutputPath> | ||||
| 		<IntermediateOutputPath>obj\$(Configuration)\</IntermediateOutputPath> | ||||
|  | ||||
| 	</PropertyGroup> | ||||
| 	 | ||||
| 	<ItemGroup> | ||||
|   | ||||
| @@ -1,16 +1,21 @@ | ||||
| <Project Sdk="Microsoft.NET.Sdk.Razor"> | ||||
|  | ||||
| 	<Import Project="..\..\Version.props" /> | ||||
| 	<Import Project="..\..\PackNuget.props" /> | ||||
| 	 | ||||
| 	<PropertyGroup> | ||||
| 		<TargetFrameworks>net8.0;</TargetFrameworks> | ||||
|  | ||||
| 		<DefineConstants>$(DefineConstants);Management</DefineConstants> | ||||
|  | ||||
| 		<OutputPath>bin\$(Configuration)\Management\</OutputPath> | ||||
| 		<IntermediateOutputPath>obj\$(Configuration)\Management\$(TargetFramework)\</IntermediateOutputPath> | ||||
| 		<OutputPath>bin\$(Configuration)\Management</OutputPath> | ||||
| 		<IntermediateOutputPath>obj\Management\$(Configuration)\</IntermediateOutputPath> | ||||
| 		<!--<BaseIntermediateOutputPath>obj\Management\</BaseIntermediateOutputPath> | ||||
| 		<MSBuildProjectExtensionsPath>$(BaseIntermediateOutputPath)</MSBuildProjectExtensionsPath>--> | ||||
| 		<PackageOutputPath>$(MSBuildThisFileDirectory)..\..\..\..\nupkgs1</PackageOutputPath> | ||||
| 	</PropertyGroup> | ||||
|  | ||||
| 	 | ||||
|  | ||||
| 	<PropertyGroup> | ||||
| 		<AssemblyName>ThingsGateway.Plugin.DB</AssemblyName> | ||||
| @@ -22,7 +27,9 @@ | ||||
| 	<ItemGroup> | ||||
| 		<ProjectReference Include="..\..\Foundation\ThingsGateway.Foundation.Razor\ThingsGateway.Foundation.Razor.csproj"> | ||||
| 		</ProjectReference> | ||||
| 		<ProjectReference Include="..\..\PluginPro\ThingsGateway.Management.Razor\ThingsGateway.Management.Razor.csproj" /> | ||||
| 		<ProjectReference Include="..\..\PluginPro\ThingsGateway.Management.Razor\ThingsGateway.Management.Razor.csproj" > | ||||
| 			<PrivateAssets>contentFiles;compile;build;buildMultitargeting;buildTransitive;analyzers;</PrivateAssets> | ||||
| 		</ProjectReference> | ||||
| 		 | ||||
| 		<PackageReference Include="Riok.Mapperly" Version="4.2.1" ExcludeAssets="runtime" PrivateAssets="all" /> | ||||
|  | ||||
|   | ||||
| @@ -5,7 +5,7 @@ | ||||
| 	<Target Name="IncludeAllFilesInTargetDir" AfterTargets="Build"> | ||||
|  | ||||
| 		<ItemGroup> | ||||
| 			<Content Include="$(ProjectDir)bin\$(Configuration)\$(TargetFramework)\**\*Dlt645*.dll"> | ||||
| 			<Content Include="$(ProjectDir)$(OutputPath)\$(TargetFramework)\**\*Dlt645*.dll"> | ||||
| 				<Pack>true</Pack> | ||||
| 				<PackagePath>Content</PackagePath> | ||||
| 			</Content> | ||||
|   | ||||
| @@ -3,7 +3,9 @@ | ||||
| 	<Import Project="..\..\PackNuget.props" /> | ||||
| 	<PropertyGroup> | ||||
| 		<TargetFrameworks>net8.0;</TargetFrameworks> | ||||
| 		 | ||||
| 		<OutputPath>bin\$(Configuration)</OutputPath> | ||||
| 		<IntermediateOutputPath>obj\$(Configuration)\</IntermediateOutputPath> | ||||
|  | ||||
| 	</PropertyGroup> | ||||
| 	<ItemGroup> | ||||
| 		<ProjectReference Include="..\..\Foundation\ThingsGateway.Foundation.Razor\ThingsGateway.Foundation.Razor.csproj"> | ||||
|   | ||||
| @@ -6,8 +6,10 @@ | ||||
|  | ||||
|  | ||||
| 		<DefineConstants>$(DefineConstants);Management</DefineConstants> | ||||
| 		<OutputPath>bin\$(Configuration)\Management\</OutputPath> | ||||
| 		<IntermediateOutputPath>obj\$(Configuration)\Management\$(TargetFramework)\</IntermediateOutputPath> | ||||
| 		<OutputPath>bin\$(Configuration)\Management</OutputPath> | ||||
| 		<IntermediateOutputPath>obj\Management\$(Configuration)\</IntermediateOutputPath> | ||||
| 		<!--<BaseIntermediateOutputPath>obj\Management\</BaseIntermediateOutputPath> | ||||
| 		<MSBuildProjectExtensionsPath>$(BaseIntermediateOutputPath)</MSBuildProjectExtensionsPath>--> | ||||
| 		<PackageOutputPath>$(MSBuildThisFileDirectory)..\..\..\..\nupkgs1</PackageOutputPath> | ||||
| 	</PropertyGroup> | ||||
|  | ||||
| @@ -21,7 +23,9 @@ | ||||
| 	<ItemGroup> | ||||
| 		<ProjectReference Include="..\..\Foundation\ThingsGateway.Foundation.Razor\ThingsGateway.Foundation.Razor.csproj"> | ||||
| 		</ProjectReference> | ||||
| 		<ProjectReference Include="..\..\PluginPro\ThingsGateway.Management.Razor\ThingsGateway.Management.Razor.csproj" /> | ||||
| 		<ProjectReference Include="..\..\PluginPro\ThingsGateway.Management.Razor\ThingsGateway.Management.Razor.csproj" > | ||||
| 			<PrivateAssets>contentFiles;compile;build;buildMultitargeting;buildTransitive;analyzers;</PrivateAssets> | ||||
| 		</ProjectReference> | ||||
| 		 | ||||
| 		<ProjectReference Include="..\ThingsGateway.Foundation.Dlt645\ThingsGateway.Foundation.Dlt645.csproj"> | ||||
| 			<PrivateAssets>contentFiles;compile;build;buildMultitargeting;buildTransitive;analyzers;</PrivateAssets> | ||||
|   | ||||
| @@ -5,7 +5,7 @@ | ||||
| 	<Target Name="IncludeAllFilesInTargetDir" AfterTargets="Build"> | ||||
|  | ||||
| 		<ItemGroup> | ||||
| 			<Content Include="$(ProjectDir)bin\$(Configuration)\$(TargetFramework)\**\*Http*.dll"> | ||||
| 			<Content Include="$(ProjectDir)$(OutputPath)\$(TargetFramework)\**\*Http*.dll"> | ||||
| 				<Pack>true</Pack> | ||||
| 				<PackagePath>Content</PackagePath> | ||||
| 			</Content> | ||||
|   | ||||
| @@ -3,7 +3,9 @@ | ||||
| 	<Import Project="..\..\PackNuget.props" /> | ||||
| 	<PropertyGroup> | ||||
| 		<TargetFrameworks>net8.0;</TargetFrameworks> | ||||
| 		 | ||||
| 		<OutputPath>bin\$(Configuration)</OutputPath> | ||||
| 		<IntermediateOutputPath>obj\$(Configuration)\</IntermediateOutputPath> | ||||
|  | ||||
| 	</PropertyGroup> | ||||
| 	<ItemGroup> | ||||
| 		<ProjectReference Include="..\..\Gateway\ThingsGateway.Gateway.Application\ThingsGateway.Gateway.Application.csproj"> | ||||
|   | ||||
| @@ -5,8 +5,10 @@ | ||||
| 		<TargetFrameworks>net8.0;</TargetFrameworks> | ||||
|  | ||||
| 		<DefineConstants>$(DefineConstants);Management</DefineConstants> | ||||
| 		<OutputPath>bin\$(Configuration)\Management\</OutputPath> | ||||
| 		<IntermediateOutputPath>obj\$(Configuration)\Management\$(TargetFramework)\</IntermediateOutputPath> | ||||
| 		<OutputPath>bin\$(Configuration)\Management</OutputPath> | ||||
| 		<IntermediateOutputPath>obj\Management\$(Configuration)\</IntermediateOutputPath> | ||||
| 		<!--<BaseIntermediateOutputPath>obj\Management\</BaseIntermediateOutputPath> | ||||
| 		<MSBuildProjectExtensionsPath>$(BaseIntermediateOutputPath)</MSBuildProjectExtensionsPath>--> | ||||
| 		<PackageOutputPath>$(MSBuildThisFileDirectory)..\..\..\..\nupkgs1</PackageOutputPath> | ||||
| 	</PropertyGroup> | ||||
|  | ||||
| @@ -17,7 +19,9 @@ | ||||
| 	</PropertyGroup> | ||||
| 	 | ||||
| 	<ItemGroup> | ||||
| 		<ProjectReference Include="..\..\PluginPro\ThingsGateway.Management.Razor\ThingsGateway.Management.Razor.csproj" /> | ||||
| 		<ProjectReference Include="..\..\PluginPro\ThingsGateway.Management.Razor\ThingsGateway.Management.Razor.csproj" > | ||||
| 			<PrivateAssets>contentFiles;compile;build;buildMultitargeting;buildTransitive;analyzers;</PrivateAssets> | ||||
| 		</ProjectReference> | ||||
| 	</ItemGroup> | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -17,7 +17,7 @@ | ||||
| 	<!--在构建后触发的。它通过在 Nuget 包的 Content 文件夹中包含目标目录中的所有文件和子文件夹来创建 nuget 包--> | ||||
| 	<Target Name="IncludeAllFilesInTargetDir" AfterTargets="Build"> | ||||
| 		<ItemGroup> | ||||
| 			<Content Include="$(ProjectDir)bin\$(Configuration)\$(TargetFramework)\**\*Kafka*.dll"> | ||||
| 			<Content Include="$(ProjectDir)$(OutputPath)\$(TargetFramework)\**\*Kafka*.dll"> | ||||
| 				<Pack>true</Pack> | ||||
| 				<PackagePath>Content</PackagePath> | ||||
| 			</Content> | ||||
|   | ||||
| @@ -3,7 +3,9 @@ | ||||
| 	<Import Project="..\..\PackNuget.props" /> | ||||
| 	<PropertyGroup> | ||||
| 		<TargetFrameworks>net8.0;</TargetFrameworks> | ||||
| 		 | ||||
| 		<OutputPath>bin\$(Configuration)</OutputPath> | ||||
| 		<IntermediateOutputPath>obj\$(Configuration)\</IntermediateOutputPath> | ||||
|  | ||||
| 	</PropertyGroup> | ||||
| 	<ItemGroup> | ||||
| 		<ProjectReference Include="..\..\Gateway\ThingsGateway.Gateway.Application\ThingsGateway.Gateway.Application.csproj"> | ||||
|   | ||||
| @@ -5,8 +5,10 @@ | ||||
| 		<TargetFrameworks>net8.0;</TargetFrameworks> | ||||
|  | ||||
| 		<DefineConstants>$(DefineConstants);Management</DefineConstants> | ||||
| 		<OutputPath>bin\$(Configuration)\Management\</OutputPath> | ||||
| 		<IntermediateOutputPath>obj\$(Configuration)\Management\$(TargetFramework)\</IntermediateOutputPath> | ||||
| 		<OutputPath>bin\$(Configuration)\Management</OutputPath> | ||||
| 		<IntermediateOutputPath>obj\Management\$(Configuration)\</IntermediateOutputPath> | ||||
| 		<!--<BaseIntermediateOutputPath>obj\Management\</BaseIntermediateOutputPath> | ||||
| 		<MSBuildProjectExtensionsPath>$(BaseIntermediateOutputPath)</MSBuildProjectExtensionsPath>--> | ||||
| 		<PackageOutputPath>$(MSBuildThisFileDirectory)..\..\..\..\nupkgs1</PackageOutputPath> | ||||
| 	</PropertyGroup> | ||||
|  | ||||
| @@ -17,7 +19,9 @@ | ||||
| 	</PropertyGroup> | ||||
| 	 | ||||
| 	<ItemGroup> | ||||
| 		<ProjectReference Include="..\..\PluginPro\ThingsGateway.Management.Razor\ThingsGateway.Management.Razor.csproj" /> | ||||
| 		<ProjectReference Include="..\..\PluginPro\ThingsGateway.Management.Razor\ThingsGateway.Management.Razor.csproj" > | ||||
| 			<PrivateAssets>contentFiles;compile;build;buildMultitargeting;buildTransitive;analyzers;</PrivateAssets> | ||||
| 		</ProjectReference> | ||||
| 		 | ||||
| 		<PackageReference Include="Confluent.Kafka" Version="2.11.0" GeneratePathProperty="true"> | ||||
| 			<PrivateAssets>contentFiles;compile;build;buildMultitargeting;buildTransitive;analyzers;</PrivateAssets> | ||||
|   | ||||
| @@ -5,7 +5,7 @@ | ||||
| 	<Target Name="IncludeAllFilesInTargetDir" AfterTargets="Build"> | ||||
|  | ||||
| 		<ItemGroup> | ||||
| 			<Content Include="$(ProjectDir)bin\$(Configuration)\$(TargetFramework)\**\*Modbus*.dll"> | ||||
| 			<Content Include="$(ProjectDir)$(OutputPath)\$(TargetFramework)\**\*Modbus*.dll"> | ||||
| 				<Pack>true</Pack> | ||||
| 				<PackagePath>Content</PackagePath> | ||||
| 			</Content> | ||||
|   | ||||
| @@ -215,12 +215,10 @@ public class ModbusSlave : BusinessBase | ||||
|  | ||||
|                 var thingsGatewayBitConverter = bitConverter.GetTransByAddress(addressStr); | ||||
|  | ||||
|                 var writeData = modbusRequest.Data.ToArray(); | ||||
|  | ||||
|                 var bitIndex = _plc.GetBitOffset(addressStr); | ||||
|                 if (modbusRequest.FunctionCode == 0x03 && dType == DataTypeEnum.Boolean && bitIndex != null) | ||||
|                 { | ||||
|                     var int16Data = thingsGatewayBitConverter.ToUInt16(writeData, 0); | ||||
|                     var int16Data = thingsGatewayBitConverter.ToUInt16(modbusRequest.MasterWriteDatas.Span, 0); | ||||
|                     var wData = BitHelper.GetBit(int16Data, bitIndex.Value); | ||||
|  | ||||
|                     var result = await item.Value.RpcAsync(wData.ToSystemTextJsonString(), $"{nameof(ModbusSlave)}-{CurrentDevice.Name}-{$"{channel}"}").ConfigureAwait(false); | ||||
| @@ -230,7 +228,7 @@ public class ModbusSlave : BusinessBase | ||||
|                 } | ||||
|                 else | ||||
|                 { | ||||
|                     _ = thingsGatewayBitConverter.GetChangedDataFormBytes(_plc, addressStr, writeData, 0, dType, item.Value.ArrayLength ?? 1, null, out var data); | ||||
|                     _ = thingsGatewayBitConverter.GetChangedDataFormBytes(_plc, addressStr, modbusRequest.MasterWriteDatas.Span, 0, dType, item.Value.ArrayLength ?? 1, null, out var data); | ||||
|  | ||||
|                     var result = await item.Value.RpcAsync(data.ToSystemTextJsonString(), $"{nameof(ModbusSlave)}-{CurrentDevice.Name}-{$"{channel}"}").ConfigureAwait(false); | ||||
|  | ||||
|   | ||||
| @@ -3,7 +3,9 @@ | ||||
| 	<Import Project="..\..\PackNuget.props" /> | ||||
| 	<PropertyGroup> | ||||
| 		<TargetFrameworks>net8.0;</TargetFrameworks> | ||||
| 		 | ||||
|  | ||||
| 		<OutputPath>bin\$(Configuration)</OutputPath> | ||||
| 		<IntermediateOutputPath>obj\$(Configuration)\</IntermediateOutputPath> | ||||
| 	</PropertyGroup> | ||||
| 	<ItemGroup> | ||||
| 		<ProjectReference Include="..\..\Foundation\ThingsGateway.Foundation.Razor\ThingsGateway.Foundation.Razor.csproj"> | ||||
|   | ||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user