mirror of
https://gitee.com/ThingsGateway/ThingsGateway.git
synced 2025-10-29 06:33:58 +08:00
@@ -46,18 +46,23 @@ public class ModbusBenchmark : IDisposable
|
||||
};
|
||||
thingsgatewaymodbus.Channel.Connect();
|
||||
}
|
||||
//{
|
||||
// var clientConfig = new TouchSocket.Core.TouchSocketConfig().SetRemoteIPHost("127.0.0.1:502");
|
||||
// modbusTcpMaster = new();
|
||||
// modbusTcpMaster.Setup(clientConfig);
|
||||
// modbusTcpMaster.Connect();
|
||||
//}
|
||||
//TcpClient client = new TcpClient("127.0.0.1", 502);
|
||||
//var factory = new NModbus.ModbusFactory();
|
||||
//nmodbus = factory.CreateMaster(client);
|
||||
{
|
||||
var clientConfig = new TouchSocket.Core.TouchSocketConfig().SetRemoteIPHost("127.0.0.1:502");
|
||||
modbusTcpMaster = new();
|
||||
modbusTcpMaster.Setup(clientConfig);
|
||||
modbusTcpMaster.Connect();
|
||||
}
|
||||
TcpClient client = new TcpClient("127.0.0.1", 502);
|
||||
var factory = new NModbus.ModbusFactory();
|
||||
nmodbus = factory.CreateMaster(client);
|
||||
|
||||
modbusTcpNet = new("127.0.0.1", 503);
|
||||
modbusTcpNet = new("127.0.0.1", 502);
|
||||
modbusTcpNet.ConnectServer();
|
||||
|
||||
thingsgatewaymodbus.ReadAsync("40001", 100).GetFalseAwaitResult();
|
||||
nmodbus.ReadHoldingRegistersAsync(1, 0, 100).GetFalseAwaitResult();
|
||||
modbusTcpMaster.ReadHoldingRegistersAsync(1, 0, 100, 3000, CancellationToken.None).GetFalseAwaitResult();
|
||||
modbusTcpNet.ReadAsync("0", 100).GetFalseAwaitResult();
|
||||
}
|
||||
|
||||
//[Benchmark]
|
||||
@@ -127,6 +132,29 @@ public class ModbusBenchmark : IDisposable
|
||||
}
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public async Task NModbus4()
|
||||
{
|
||||
List<Task> tasks = new List<Task>();
|
||||
for (int i = 0; i < Program.NumberOfItems; i++)
|
||||
{
|
||||
var result = await nmodbus.ReadHoldingRegistersAsync(1, 0, 100);
|
||||
}
|
||||
await Task.WhenAll(tasks);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public async Task TouchSocket()
|
||||
{
|
||||
List<Task> tasks = new List<Task>();
|
||||
for (int i = 0; i < Program.NumberOfItems; i++)
|
||||
{
|
||||
var result = await modbusTcpMaster.ReadHoldingRegistersAsync(1, 0, 100, 3000, CancellationToken.None);
|
||||
}
|
||||
|
||||
await Task.WhenAll(tasks);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public async Task HslCommunication()
|
||||
{
|
||||
|
||||
@@ -1,70 +1,70 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://kimdiego2098.github.io/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
////------------------------------------------------------------------------------
|
||||
//// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
//// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
//// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
//// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
//// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
//// 使用文档:https://kimdiego2098.github.io/
|
||||
//// QQ群:605534569
|
||||
////------------------------------------------------------------------------------
|
||||
|
||||
using BenchmarkConsoleApp;
|
||||
//using BenchmarkConsoleApp;
|
||||
|
||||
using BenchmarkDotNet.Attributes;
|
||||
//using BenchmarkDotNet.Attributes;
|
||||
|
||||
using ThingsGateway.Foundation.Modbus;
|
||||
using ThingsGateway.Foundation.SiemensS7;
|
||||
//using ThingsGateway.Foundation.Modbus;
|
||||
//using ThingsGateway.Foundation.SiemensS7;
|
||||
|
||||
using TouchSocket.Core;
|
||||
using TouchSocket.Modbus;
|
||||
using TouchSocket.Sockets;
|
||||
//using TouchSocket.Core;
|
||||
//using TouchSocket.Modbus;
|
||||
//using TouchSocket.Sockets;
|
||||
|
||||
namespace ThingsGateway.Foundation;
|
||||
//namespace ThingsGateway.Foundation;
|
||||
|
||||
[MemoryDiagnoser]
|
||||
[ThreadingDiagnoser]
|
||||
public class S7Benchmark : IDisposable
|
||||
{
|
||||
private SiemensS7Master siemensS7;
|
||||
//[MemoryDiagnoser]
|
||||
//[ThreadingDiagnoser]
|
||||
//public class S7Benchmark : IDisposable
|
||||
//{
|
||||
// private SiemensS7Master siemensS7;
|
||||
|
||||
public S7Benchmark()
|
||||
{
|
||||
{
|
||||
var clientConfig = new TouchSocket.Core.TouchSocketConfig();
|
||||
var clientChannel = clientConfig.GetTcpClientWithIPHost("127.0.0.1:102");
|
||||
siemensS7 = new(clientChannel)
|
||||
{
|
||||
//modbus协议格式
|
||||
SiemensS7Type = SiemensTypeEnum.S1500
|
||||
};
|
||||
siemensS7.Channel.Connect();
|
||||
}
|
||||
}
|
||||
// public S7Benchmark()
|
||||
// {
|
||||
// {
|
||||
// var clientConfig = new TouchSocket.Core.TouchSocketConfig();
|
||||
// var clientChannel = clientConfig.GetTcpClientWithIPHost("127.0.0.1:102");
|
||||
// siemensS7 = new(clientChannel)
|
||||
// {
|
||||
// //modbus协议格式
|
||||
// SiemensS7Type = SiemensTypeEnum.S1500
|
||||
// };
|
||||
// siemensS7.Channel.Connect();
|
||||
// }
|
||||
// }
|
||||
|
||||
[Benchmark]
|
||||
public async Task ThingsGateway()
|
||||
{
|
||||
List<Task> tasks = new List<Task>();
|
||||
for (int i = 0; i < Program.TaskNumberOfItems; i++)
|
||||
{
|
||||
tasks.Add(Task.Run(async () =>
|
||||
{
|
||||
for (int i = 0; i < Program.NumberOfItems; i++)
|
||||
{
|
||||
var result = await siemensS7.ReadAsync("M1", 1000);
|
||||
if (!result.IsSuccess)
|
||||
{
|
||||
throw new Exception(result.ToString());
|
||||
}
|
||||
}
|
||||
}));
|
||||
}
|
||||
await Task.WhenAll(tasks);
|
||||
}
|
||||
// [Benchmark]
|
||||
// public async Task ThingsGateway()
|
||||
// {
|
||||
// List<Task> tasks = new List<Task>();
|
||||
// for (int i = 0; i < Program.TaskNumberOfItems; i++)
|
||||
// {
|
||||
// tasks.Add(Task.Run(async () =>
|
||||
// {
|
||||
// for (int i = 0; i < Program.NumberOfItems; i++)
|
||||
// {
|
||||
// var result = await siemensS7.ReadAsync("M1", 1000);
|
||||
// if (!result.IsSuccess)
|
||||
// {
|
||||
// throw new Exception(result.ToString());
|
||||
// }
|
||||
// }
|
||||
// }));
|
||||
// }
|
||||
// await Task.WhenAll(tasks);
|
||||
// }
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
siemensS7.Channel.SafeDispose();
|
||||
siemensS7.SafeDispose();
|
||||
}
|
||||
}
|
||||
// public void Dispose()
|
||||
// {
|
||||
// siemensS7.Channel.SafeDispose();
|
||||
// siemensS7.SafeDispose();
|
||||
// }
|
||||
//}
|
||||
@@ -11,13 +11,10 @@
|
||||
<ItemGroup>
|
||||
<PackageReference Include="BenchmarkDotNet" Version="0.13.12" />
|
||||
<PackageReference Include="HslCommunication" Version="12.0.2" />
|
||||
<PackageReference Include="MemoryPack" Version="1.10.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
|
||||
|
||||
<PackageReference Include="NModbus" Version="3.0.81" />
|
||||
<PackageReference Include="ThingsGateway.Foundation.Modbus" Version="6.0.3.47" />
|
||||
<PackageReference Include="ThingsGateway.Foundation.SiemensS7" Version="6.0.3.47" />
|
||||
<PackageReference Include="TouchSocket.Modbus" Version="2.1.0-alpha.21" />
|
||||
<PackageReference Include="ThingsGateway.Foundation.Modbus" Version="6.0.3.50" />
|
||||
<!--<PackageReference Include="ThingsGateway.Foundation.SiemensS7" Version="6.0.3.47" />-->
|
||||
<PackageReference Include="TouchSocket.Modbus" Version="2.1.0-alpha.22" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -49,20 +49,20 @@ namespace BenchmarkConsoleApp
|
||||
{
|
||||
BenchmarkRunner.Run(typeof(TaskCompletionSourceBenchmark));
|
||||
});
|
||||
consoleAction.Add("3", "S7测试", async () =>
|
||||
{
|
||||
S7Benchmark s7Benchmark = new();
|
||||
//consoleAction.Add("3", "S7测试", async () =>
|
||||
//{
|
||||
// S7Benchmark s7Benchmark = new();
|
||||
|
||||
Stopwatch stopwatch = new();
|
||||
stopwatch.Start();
|
||||
await s7Benchmark.ThingsGateway();
|
||||
stopwatch.Stop();
|
||||
Console.WriteLine(stopwatch.ElapsedMilliseconds);
|
||||
});
|
||||
// Stopwatch stopwatch = new();
|
||||
// stopwatch.Start();
|
||||
// await s7Benchmark.ThingsGateway();
|
||||
// stopwatch.Stop();
|
||||
// Console.WriteLine(stopwatch.ElapsedMilliseconds);
|
||||
//});
|
||||
|
||||
consoleAction.Add("1001", "单独测试 ThingsGateway", async () => { await ThingsGateway(); });
|
||||
//consoleAction.Add("1002", "单独测试 NModbus4", async () => { await NModbus4(); });
|
||||
//consoleAction.Add("1003", "单独测试 TouchSocket", async () => { await TouchSocket(); });
|
||||
consoleAction.Add("1002", "单独测试 NModbus4", async () => { await NModbus4(); });
|
||||
consoleAction.Add("1003", "单独测试 TouchSocket", async () => { await TouchSocket(); });
|
||||
consoleAction.Add("1004", "单独测试 TaskCompletionSource", async () =>
|
||||
{
|
||||
TaskCompletionSourceBenchmark taskCompletionSourceBenchmark = new();
|
||||
@@ -114,58 +114,58 @@ namespace BenchmarkConsoleApp
|
||||
}
|
||||
}
|
||||
|
||||
//private static async Task TouchSocket()
|
||||
//{
|
||||
// try
|
||||
// {
|
||||
// Console.WriteLine(" TouchSocket 测试已开始");
|
||||
// Stopwatch stopwatch = new();
|
||||
// stopwatch.Start();
|
||||
// List<Task> tasks = new List<Task>();
|
||||
// for (int i = 0; i < ClientCount; i++)
|
||||
// {
|
||||
// tasks.Add(Task.Run(async () =>
|
||||
// {
|
||||
// ModbusBenchmark modbusBenchmarker = new();
|
||||
// await modbusBenchmarker.TouchSocket();
|
||||
// modbusBenchmarker.Dispose();
|
||||
// }));
|
||||
// }
|
||||
// await Task.WhenAll(tasks);
|
||||
// stopwatch.Stop();
|
||||
// Console.WriteLine($" TouchSocket 耗时:{stopwatch.ElapsedMilliseconds}");
|
||||
// }
|
||||
// catch (Exception ex)
|
||||
// {
|
||||
// Console.WriteLine($" TouchSocket 发生错误:{ex.Message}");
|
||||
// }
|
||||
//}
|
||||
private static async Task TouchSocket()
|
||||
{
|
||||
try
|
||||
{
|
||||
Console.WriteLine(" TouchSocket 测试已开始");
|
||||
Stopwatch stopwatch = new();
|
||||
stopwatch.Start();
|
||||
List<Task> tasks = new List<Task>();
|
||||
for (int i = 0; i < ClientCount; i++)
|
||||
{
|
||||
tasks.Add(Task.Run(async () =>
|
||||
{
|
||||
ModbusBenchmark modbusBenchmarker = new();
|
||||
await modbusBenchmarker.TouchSocket();
|
||||
modbusBenchmarker.Dispose();
|
||||
}));
|
||||
}
|
||||
await Task.WhenAll(tasks);
|
||||
stopwatch.Stop();
|
||||
Console.WriteLine($" TouchSocket 耗时:{stopwatch.ElapsedMilliseconds}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($" TouchSocket 发生错误:{ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
//private static async Task NModbus4()
|
||||
//{
|
||||
// try
|
||||
// {
|
||||
// Console.WriteLine(" NModbus4 测试已开始");
|
||||
// Stopwatch stopwatch = new();
|
||||
// stopwatch.Start();
|
||||
// List<Task> tasks = new List<Task>();
|
||||
// for (int i = 0; i < ClientCount; i++)
|
||||
// {
|
||||
// tasks.Add(Task.Run(async () =>
|
||||
// {
|
||||
// ModbusBenchmark modbusBenchmarker = new();
|
||||
// await modbusBenchmarker.NModbus4();
|
||||
// modbusBenchmarker.Dispose();
|
||||
// }));
|
||||
// }
|
||||
// await Task.WhenAll(tasks);
|
||||
// stopwatch.Stop();
|
||||
// Console.WriteLine($" NModbus4 耗时:{stopwatch.ElapsedMilliseconds}");
|
||||
// }
|
||||
// catch (Exception ex)
|
||||
// {
|
||||
// Console.WriteLine($" NModbus4 发生错误:{ex.Message}");
|
||||
// }
|
||||
//}
|
||||
private static async Task NModbus4()
|
||||
{
|
||||
try
|
||||
{
|
||||
Console.WriteLine(" NModbus4 测试已开始");
|
||||
Stopwatch stopwatch = new();
|
||||
stopwatch.Start();
|
||||
List<Task> tasks = new List<Task>();
|
||||
for (int i = 0; i < ClientCount; i++)
|
||||
{
|
||||
tasks.Add(Task.Run(async () =>
|
||||
{
|
||||
ModbusBenchmark modbusBenchmarker = new();
|
||||
await modbusBenchmarker.NModbus4();
|
||||
modbusBenchmarker.Dispose();
|
||||
}));
|
||||
}
|
||||
await Task.WhenAll(tasks);
|
||||
stopwatch.Stop();
|
||||
Console.WriteLine($" NModbus4 耗时:{stopwatch.ElapsedMilliseconds}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($" NModbus4 发生错误:{ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -72,13 +72,13 @@ public static class UnifyContext
|
||||
/// <summary>
|
||||
/// 跳过规范化处理的 Response Content-Type
|
||||
/// </summary>
|
||||
internal static string[] ResponseContentTypesOfNonUnify = new[]
|
||||
{
|
||||
internal static string[] ResponseContentTypesOfNonUnify =
|
||||
[
|
||||
"text/event-stream",
|
||||
"application/pdf",
|
||||
"application/octet-stream",
|
||||
"image/"
|
||||
};
|
||||
];
|
||||
|
||||
/// <summary>
|
||||
/// 检查 HttpContext 是否进行规范化处理
|
||||
|
||||
@@ -435,8 +435,8 @@ public static class ObjectExtensions
|
||||
{
|
||||
if (str == null) return Array.Empty<string>();
|
||||
|
||||
if (string.IsNullOrWhiteSpace(str)) return new string[] { str };
|
||||
if (str.Length == 1) return new string[] { str };
|
||||
if (string.IsNullOrWhiteSpace(str)) return [str];
|
||||
if (str.Length == 1) return [str];
|
||||
|
||||
return Regex.Split(str, @"(?=\p{Lu}\p{Ll})|(?<=\p{Ll})(?=\p{Lu})")
|
||||
.Where(u => u.Length > 0)
|
||||
|
||||
@@ -33,8 +33,7 @@ public static class SqlSugarExtensions
|
||||
public static ISugarQueryable<TEntity> ExportIgnoreColumns<TEntity>(this ISugarQueryable<TEntity> queryable)
|
||||
{
|
||||
return queryable.IgnoreColumns(
|
||||
new string[]
|
||||
{
|
||||
[
|
||||
nameof(BaseEntity.Id),
|
||||
nameof(BaseEntity.CreateTime),
|
||||
nameof(BaseEntity.CreateUser),
|
||||
@@ -44,7 +43,7 @@ public static class SqlSugarExtensions
|
||||
nameof(BaseEntity.UpdateTime),
|
||||
nameof(BaseEntity.UpdateUser),
|
||||
nameof(BaseEntity.UpdateUserId),
|
||||
}
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -31,19 +31,19 @@ public static class StartupExtensions
|
||||
/// <summary>
|
||||
/// 排除的配置文件前缀
|
||||
/// </summary>
|
||||
private static readonly string[] excludeJsonPrefixs = new[] { "appsettings", "bundleconfig", "compilerconfig" };
|
||||
private static readonly string[] excludeJsonPrefixs = ["appsettings", "bundleconfig", "compilerconfig"];
|
||||
|
||||
/// <summary>
|
||||
/// 排除运行时 Json 后缀
|
||||
/// </summary>
|
||||
private static readonly string[] runtimeJsonSuffixs = new string[]
|
||||
{
|
||||
private static readonly string[] runtimeJsonSuffixs =
|
||||
[
|
||||
"deps.json",
|
||||
"runtimeconfig.dev.json",
|
||||
"runtimeconfig.prod.json",
|
||||
"runtimeconfig.json",
|
||||
"staticwebassets.runtime.json"
|
||||
};
|
||||
];
|
||||
|
||||
/// <summary>
|
||||
/// 对配置文件名进行分组
|
||||
|
||||
@@ -42,7 +42,7 @@ public class FileService : IFileService
|
||||
if (file == null) throw Oops.Bah("FileNullError");
|
||||
if (file.Size > maxSize * 1024 * 1024) throw Oops.Bah("FileLengthError", maxSize);
|
||||
var fileSuffix = Path.GetExtension(file.Name).ToLower().Split(".")[1]; // 文件后缀
|
||||
string[] allowTypeS = allowTypes == null ? new string[] { "xlsx" } : allowTypes;//允许上传的文件类型
|
||||
string[] allowTypeS = allowTypes == null ? ["xlsx"] : allowTypes;//允许上传的文件类型
|
||||
if (!allowTypeS.Contains(fileSuffix)) throw Oops.Bah("FileTypeError", fileSuffix);
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<PackageReference Include="UAParser" Version="3.1.47" />
|
||||
<PackageReference Include="SqlSugarCore" Version="5.1.4.159" />
|
||||
<PackageReference Include="Mapster" Version="7.4.0" />
|
||||
<PackageReference Include="BootstrapBlazor" Version="8.6.3" />
|
||||
<PackageReference Include="BootstrapBlazor" Version="8.6.4" />
|
||||
<PackageReference Include="Yitter.IdGenerator" Version="1.0.14" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ public class JWTEncryption
|
||||
/// <summary>
|
||||
/// 刷新 Token 身份标识
|
||||
/// </summary>
|
||||
private static readonly string[] _refreshTokenClaims = new[] { "f", "e", "s", "l", "k" };
|
||||
private static readonly string[] _refreshTokenClaims = ["f", "e", "s", "l", "k"];
|
||||
|
||||
/// <summary>
|
||||
/// 生成 Token
|
||||
@@ -244,7 +244,7 @@ public class JWTEncryption
|
||||
|
||||
// 处理 axios 问题
|
||||
httpContext.Response.Headers.TryGetValue(accessControlExposeKey, out var acehs);
|
||||
httpContext.Response.Headers[accessControlExposeKey] = string.Join(',', StringValues.Concat(acehs, new StringValues(new[] { accessTokenKey, xAccessTokenKey })).Distinct());
|
||||
httpContext.Response.Headers[accessControlExposeKey] = string.Join(',', StringValues.Concat(acehs, new StringValues([accessTokenKey, xAccessTokenKey])).Distinct());
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -482,7 +482,7 @@ public class JWTEncryption
|
||||
/// <summary>
|
||||
/// 日期类型的 Claim 类型
|
||||
/// </summary>
|
||||
private static readonly string[] DateTypeClaimTypes = new[] { JwtRegisteredClaimNames.Iat, JwtRegisteredClaimNames.Nbf, JwtRegisteredClaimNames.Exp };
|
||||
private static readonly string[] DateTypeClaimTypes = [JwtRegisteredClaimNames.Iat, JwtRegisteredClaimNames.Nbf, JwtRegisteredClaimNames.Exp];
|
||||
|
||||
/// <summary>
|
||||
/// 框架 App 静态类
|
||||
|
||||
@@ -91,8 +91,8 @@ public static class StringExtensions
|
||||
{
|
||||
if (str == null) return Array.Empty<string>();
|
||||
|
||||
if (string.IsNullOrWhiteSpace(str)) return new string[] { str };
|
||||
if (str.Length == 1) return new string[] { str };
|
||||
if (string.IsNullOrWhiteSpace(str)) return [str];
|
||||
if (str.Length == 1) return [str];
|
||||
|
||||
return Regex.Split(str, @"(?=\p{Lu}\p{Ll})|(?<=\p{Ll})(?=\p{Lu})")
|
||||
.Where(u => u.Length > 0)
|
||||
|
||||
@@ -23,6 +23,7 @@ public interface IClientChannel : IChannel, ISender, IClient, IClientSender, IOn
|
||||
AsyncAutoResetEvent WaitLock { get; }
|
||||
|
||||
WaitHandlePool<MessageBase> WaitHandlePool { get; }
|
||||
DataHandlingAdapter ReadOnlyDataHandlingAdapter { get; }
|
||||
|
||||
#endregion 属性
|
||||
}
|
||||
|
||||
@@ -25,6 +25,8 @@ public class SerialPortChannel : SerialPortClient, IClientChannel
|
||||
/// <inheritdoc/>
|
||||
public AsyncAutoResetEvent WaitLock { get; } = new AsyncAutoResetEvent(true);
|
||||
|
||||
public DataHandlingAdapter ReadOnlyDataHandlingAdapter => ProtectedDataHandlingAdapter;
|
||||
|
||||
/// <summary>
|
||||
/// 等待池
|
||||
/// </summary>
|
||||
|
||||
@@ -33,6 +33,8 @@ public class TcpClientChannel : TcpClient, IClientChannel
|
||||
/// </summary>
|
||||
public WaitHandlePool<MessageBase> WaitHandlePool { get; } = new();
|
||||
|
||||
public DataHandlingAdapter ReadOnlyDataHandlingAdapter => DataHandlingAdapter;
|
||||
|
||||
/// <summary>
|
||||
/// 接收到数据
|
||||
/// </summary>
|
||||
|
||||
@@ -26,6 +26,8 @@ public class TcpSessionClientChannel : TcpSessionClient, IClientChannel
|
||||
/// </summary>
|
||||
public WaitHandlePool<MessageBase> WaitHandlePool { get; private set; } = new();
|
||||
|
||||
public DataHandlingAdapter ReadOnlyDataHandlingAdapter => DataHandlingAdapter;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ConcurrentList<IProtocol> Collects { get; } = new();
|
||||
|
||||
|
||||
@@ -21,6 +21,8 @@ public class UdpSessionChannel : UdpSession, IClientChannel
|
||||
/// <inheritdoc/>
|
||||
public AsyncAutoResetEvent WaitLock { get; } = new AsyncAutoResetEvent(true);
|
||||
|
||||
public DataHandlingAdapter ReadOnlyDataHandlingAdapter => DataHandlingAdapter;
|
||||
|
||||
/// <summary>
|
||||
/// 等待池
|
||||
/// </summary>
|
||||
|
||||
@@ -29,7 +29,7 @@ public class ChannelData
|
||||
/// <summary>
|
||||
/// 远程地址,可由<see cref="IPHost.IPHost(string)"/>与<see href="IPHost.ToString()"/>相互转化
|
||||
/// </summary>
|
||||
public string? RemoteUrl { get; set; }
|
||||
public string? RemoteUrl { get; set; } = "127.0.0.1:502";
|
||||
|
||||
/// <summary>
|
||||
/// 本地地址,可由<see cref="IPHost.IPHost(string)"/>与<see href="IPHost.ToString()"/>相互转化
|
||||
@@ -39,17 +39,17 @@ public class ChannelData
|
||||
/// <summary>
|
||||
/// COM
|
||||
/// </summary>
|
||||
public string? PortName { get; set; }
|
||||
public string? PortName { get; set; } = "COM1";
|
||||
|
||||
/// <summary>
|
||||
/// 波特率
|
||||
/// </summary>
|
||||
public int? BaudRate { get; set; }
|
||||
public int? BaudRate { get; set; } = 9600;
|
||||
|
||||
/// <summary>
|
||||
/// 数据位
|
||||
/// </summary>
|
||||
public int? DataBits { get; set; }
|
||||
public int? DataBits { get; set; } = 8;
|
||||
|
||||
/// <summary>
|
||||
/// 校验位
|
||||
@@ -64,12 +64,12 @@ public class ChannelData
|
||||
/// <summary>
|
||||
/// DtrEnable
|
||||
/// </summary>
|
||||
public bool? DtrEnable { get; set; }
|
||||
public bool? DtrEnable { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// RtsEnable
|
||||
/// </summary>
|
||||
public bool? RtsEnable { get; set; }
|
||||
public bool? RtsEnable { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// TouchSocketConfig
|
||||
|
||||
@@ -15,42 +15,46 @@ namespace ThingsGateway.Foundation;
|
||||
/// </summary>
|
||||
public interface IResultMessage : IOperResult, IRequestInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// 消息头的指令长度,不固定时返回0
|
||||
/// </summary>
|
||||
int HeadBytesLength { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 实体数据长度,不固定是返回0
|
||||
/// </summary>
|
||||
int BodyLength { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 解析的字节信息
|
||||
/// </summary>
|
||||
byte[] Content { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 接收的字节信息
|
||||
/// </summary>
|
||||
IByteBlock ReceivedBytes { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 等待标识,对于并发协议,必须从协议中例如固定头部获取标识字段
|
||||
/// </summary>
|
||||
int Sign { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 检查头子节的合法性,并赋值<see cref="BodyLength"/><br />
|
||||
/// 数据体长度
|
||||
/// </summary>
|
||||
int BodyLength { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 消息头的指令长度,不固定时返回0
|
||||
/// </summary>
|
||||
int HeaderLength { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 当收到数据,由框架封送有效载荷数据。
|
||||
/// 此时流位置为<see cref="HeaderLength"/>
|
||||
/// <para>但是如果是因为数据错误,则需要修改<see cref="ByteBlock.Position"/>到正确位置,如果都不正确,则设置<see cref="ByteBlock.Position"/>等于<see cref="ByteBlock.Length"/></para>
|
||||
/// <para>然后返回<see cref="FilterResult.GoOn"/></para>
|
||||
/// </summary>
|
||||
/// <returns>是否成功有效</returns>
|
||||
FilterResult CheckBody<TByteBlock>(ref TByteBlock byteBlock) where TByteBlock : IByteBlock;
|
||||
|
||||
/// <summary>
|
||||
/// 检查头子节的合法性,并赋值<see cref="BodyLength"/><br />
|
||||
/// <para>如果返回false,意味着放弃本次解析的所有数据,包括已经解析完成的Header</para>
|
||||
/// </summary>
|
||||
/// <param name="headBytes">接收的头子节</param>
|
||||
/// <returns>是否成功的结果</returns>
|
||||
bool CheckHeadBytes(byte[]? headBytes);
|
||||
bool CheckHead<TByteBlock>(ref TByteBlock byteBlock) where TByteBlock : IByteBlock;
|
||||
|
||||
/// <summary>
|
||||
/// 发送前的信息处理,例如存储某些特征信息:站号/功能码等等用于验证后续的返回信息是否合法
|
||||
/// </summary>
|
||||
/// <param name="sendBytes"></param>
|
||||
/// <param name="sendMessage"></param>
|
||||
/// <returns></returns>
|
||||
void SendInfo(ReadOnlyMemory<byte> sendBytes);
|
||||
void SendInfo(ISendMessage sendMessage);
|
||||
}
|
||||
|
||||
@@ -13,7 +13,6 @@ namespace ThingsGateway.Foundation;
|
||||
/// <summary>
|
||||
/// 发送消息
|
||||
/// </summary>
|
||||
public interface ISendMessage : IRequestInfo, IWaitHandle
|
||||
public interface ISendMessage : IRequestInfo, IWaitHandle, IRequestInfoBuilder
|
||||
{
|
||||
ReadOnlyMemory<byte> SendBytes { get; }
|
||||
}
|
||||
|
||||
@@ -13,6 +13,8 @@ namespace ThingsGateway.Foundation;
|
||||
/// <inheritdoc cref="IResultMessage"/>
|
||||
public class MessageBase : OperResultClass<byte[]>, IResultMessage, IWaitHandle
|
||||
{
|
||||
#region 构造
|
||||
|
||||
public MessageBase()
|
||||
{
|
||||
}
|
||||
@@ -21,38 +23,34 @@ public class MessageBase : OperResultClass<byte[]>, IResultMessage, IWaitHandle
|
||||
{
|
||||
}
|
||||
|
||||
public MessageBase(string msg) : base(msg)
|
||||
{
|
||||
}
|
||||
|
||||
public MessageBase(Exception ex) : base(ex)
|
||||
{
|
||||
}
|
||||
|
||||
public MessageBase(string msg, Exception ex) : base(msg, ex)
|
||||
{
|
||||
}
|
||||
#endregion 构造
|
||||
|
||||
/// <inheritdoc/>
|
||||
public int BodyLength { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual int HeadBytesLength { get; }
|
||||
public virtual int HeaderLength { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual int Sign { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IByteBlock ReceivedBytes { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual bool CheckHeadBytes(byte[]? headBytes)
|
||||
public virtual bool CheckHead<TByteBlock>(ref TByteBlock byteBlock) where TByteBlock : IByteBlock
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual void SendInfo(ReadOnlyMemory<byte> sendBytes)
|
||||
public virtual void SendInfo(ISendMessage sendMessage)
|
||||
{
|
||||
}
|
||||
|
||||
public virtual FilterResult CheckBody<TByteBlock>(ref TByteBlock byteBlock) where TByteBlock : IByteBlock
|
||||
{
|
||||
return FilterResult.Success;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,10 +15,10 @@ namespace ThingsGateway.Foundation;
|
||||
/// <summary>
|
||||
/// TCP/Serial适配器基类
|
||||
/// </summary>
|
||||
public abstract class ReadWriteDevicesSingleStreamDataHandleAdapter<TRequest> : CustomDataHandlingAdapter<TRequest> where TRequest : class, IResultMessage, new()
|
||||
public class ProtocolSingleStreamDataHandleAdapter<TRequest> : CustomDataHandlingAdapter<TRequest> where TRequest : class, IResultMessage, new()
|
||||
{
|
||||
/// <inheritdoc cref="ReadWriteDevicesSingleStreamDataHandleAdapter{TRequest}"/>
|
||||
public ReadWriteDevicesSingleStreamDataHandleAdapter()
|
||||
/// <inheritdoc cref="ProtocolSingleStreamDataHandleAdapter{TRequest}"/>
|
||||
public ProtocolSingleStreamDataHandleAdapter()
|
||||
{
|
||||
CacheTimeoutEnable = true;
|
||||
}
|
||||
@@ -66,62 +66,52 @@ public abstract class ReadWriteDevicesSingleStreamDataHandleAdapter<TRequest> :
|
||||
|
||||
var pos = byteBlock.Position;
|
||||
|
||||
if (request.HeadBytesLength > byteBlock.CanReadLength)
|
||||
if (request.HeaderLength > byteBlock.CanReadLength)
|
||||
{
|
||||
return FilterResult.Cache;//当头部都无法解析时,直接缓存
|
||||
}
|
||||
ArraySegment<byte>? header = null;
|
||||
//传入新的ByteBlock对象,避免影响原有的游标
|
||||
//当解析消息设定固定头长度大于0时,获取头部字节
|
||||
if (request.HeadBytesLength > 0)
|
||||
{
|
||||
header = byteBlock.AsSegment(byteBlock.Position, request.HeadBytesLength);
|
||||
}
|
||||
else
|
||||
{
|
||||
}
|
||||
|
||||
//检查头部合法性
|
||||
if (request.CheckHeadBytes(header?.Array))
|
||||
if (request.CheckHead(ref byteBlock))
|
||||
{
|
||||
byteBlock.Position = pos;
|
||||
if (request.BodyLength > this.MaxPackageSize)
|
||||
{
|
||||
this.OnError(default, $"Received BodyLength={request.BodyLength}, greater than the set MaxPackageSize={this.MaxPackageSize}", true, true);
|
||||
return FilterResult.GoOn;
|
||||
}
|
||||
if (request.BodyLength + request.HeadBytesLength > byteBlock.CanReadLength)
|
||||
if (request.BodyLength + request.HeaderLength > byteBlock.CanReadLength)
|
||||
{
|
||||
//body不满足解析,开始缓存,然后保存对象
|
||||
tempCapacity = request.BodyLength + request.HeadBytesLength;
|
||||
tempCapacity = request.BodyLength + request.HeaderLength;
|
||||
return FilterResult.Cache;
|
||||
}
|
||||
if (request.BodyLength <= 0)
|
||||
{
|
||||
//如果body长度无法确定,直接读取全部
|
||||
request.BodyLength = byteBlock.Length;
|
||||
}
|
||||
var result = UnpackResponse(request, byteBlock);
|
||||
if (result.FilterResult == FilterResult.Cache)
|
||||
//if (request.BodyLength <= 0)
|
||||
//{
|
||||
// //如果body长度无法确定,直接读取全部
|
||||
// request.BodyLength = byteBlock.Length;
|
||||
//}
|
||||
var headPos = pos + request.HeaderLength;
|
||||
byteBlock.Position = headPos;
|
||||
var result = request.CheckBody(ref byteBlock);
|
||||
if (result == FilterResult.Cache)
|
||||
{
|
||||
if (Logger.LogLevel <= LogLevel.Trace)
|
||||
Logger.Trace($"{ToString()}-Received incomplete, cached message, current length:{byteBlock.Length} {request?.ErrorMessage}");
|
||||
tempCapacity = request.BodyLength + request.HeadBytesLength;
|
||||
tempCapacity = request.BodyLength + request.HeaderLength;
|
||||
request.OperCode = -1;
|
||||
}
|
||||
else if (result.FilterResult == FilterResult.GoOn)
|
||||
else if (result == FilterResult.GoOn)
|
||||
{
|
||||
byteBlock.Position += 1;
|
||||
if (byteBlock.Position == headPos)
|
||||
byteBlock.Position += 1;
|
||||
request.OperCode = -1;
|
||||
}
|
||||
else if (result.FilterResult == FilterResult.Success)
|
||||
else if (result == FilterResult.Success)
|
||||
{
|
||||
byteBlock.Position = request.HeadBytesLength + request.BodyLength + pos;
|
||||
if (request.IsSuccess)
|
||||
{
|
||||
request.Content = result.Content;
|
||||
request.ReceivedBytes = byteBlock;
|
||||
}
|
||||
byteBlock.Position = request.HeaderLength + request.BodyLength + pos;
|
||||
}
|
||||
return result.FilterResult;
|
||||
return result;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -160,32 +150,35 @@ public abstract class ReadWriteDevicesSingleStreamDataHandleAdapter<TRequest> :
|
||||
{
|
||||
if (!(requestInfo is ISendMessage sendMessage))
|
||||
{
|
||||
throw new Exception($"Unable to convert {nameof(requestInfo)} to {nameof(SendMessage)}");
|
||||
throw new Exception($"Unable to convert {nameof(requestInfo)} to {nameof(ISendMessage)}");
|
||||
}
|
||||
var sendData = sendMessage.SendBytes;
|
||||
|
||||
if (Logger.LogLevel <= LogLevel.Trace)
|
||||
Logger?.Trace($"{ToString()}- Send:{(IsHexData ? sendData.Span.ToHexString() : (sendData.Span.ToString(Encoding.UTF8)))}");
|
||||
//非并发主从协议
|
||||
if (IsSingleThread)
|
||||
var requestInfoBuilder = (ISendMessage)requestInfo;
|
||||
|
||||
var byteBlock = new ValueByteBlock(requestInfoBuilder.MaxLength);
|
||||
try
|
||||
{
|
||||
SetRequest(sendMessage.Sign, sendData);
|
||||
requestInfoBuilder.Build(ref byteBlock);
|
||||
if (Logger.LogLevel <= LogLevel.Trace)
|
||||
Logger?.Trace($"{ToString()}- Send:{(IsHexData ? byteBlock.Span.ToHexString() : (byteBlock.Span.ToString(Encoding.UTF8)))}");
|
||||
//非并发主从协议
|
||||
if (IsSingleThread)
|
||||
{
|
||||
SetRequest(sendMessage.Sign, requestInfoBuilder);
|
||||
}
|
||||
await this.GoSendAsync(byteBlock.Memory).ConfigureFalseAwait();
|
||||
}
|
||||
finally
|
||||
{
|
||||
byteBlock.SafeDispose();
|
||||
}
|
||||
|
||||
//发送
|
||||
await this.GoSendAsync(sendData).ConfigureFalseAwait();
|
||||
}
|
||||
|
||||
public void SetRequest(int sign, ReadOnlyMemory<byte> sendData)
|
||||
public void SetRequest(int sign, ISendMessage sendMessage)
|
||||
{
|
||||
var request = GetInstance();
|
||||
request.Sign = sign;
|
||||
request.SendInfo(sendData);
|
||||
request.SendInfo(sendMessage);
|
||||
Request = request;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 解包获取实际数据
|
||||
/// </summary>
|
||||
protected abstract AdapterResult UnpackResponse(TRequest request, IByteBlock byteBlock);
|
||||
}
|
||||
@@ -16,7 +16,7 @@ namespace ThingsGateway.Foundation;
|
||||
/// <summary>
|
||||
/// UDP适配器基类
|
||||
/// </summary>
|
||||
public abstract class ReadWriteDevicesUdpDataHandleAdapter<TRequest> : UdpDataHandlingAdapter where TRequest : class, IResultMessage, new()
|
||||
public class ProtocolUdpDataHandleAdapter<TRequest> : UdpDataHandlingAdapter where TRequest : class, IResultMessage, new()
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public override bool CanSendRequestInfo => true;
|
||||
@@ -77,52 +77,60 @@ public abstract class ReadWriteDevicesUdpDataHandleAdapter<TRequest> : UdpDataHa
|
||||
{
|
||||
request = GetInstance();
|
||||
}
|
||||
ArraySegment<byte>? header = null;
|
||||
//传入新的ByteBlock对象,避免影响原有的游标
|
||||
//当解析消息设定固定头长度大于0时,获取头部字节
|
||||
if (request.HeadBytesLength > 0)
|
||||
{
|
||||
header = byteBlock.AsSegment(0, request.HeadBytesLength);
|
||||
}
|
||||
else
|
||||
|
||||
var pos = byteBlock.Position;
|
||||
|
||||
if (request.HeaderLength > byteBlock.CanReadLength)
|
||||
{
|
||||
return;//当头部都无法解析时,直接缓存
|
||||
}
|
||||
|
||||
//检查头部合法性
|
||||
if (request.CheckHeadBytes(header?.Array))
|
||||
if (request.CheckHead(ref byteBlock))
|
||||
{
|
||||
byteBlock.Position = pos;
|
||||
|
||||
if (request.BodyLength > this.MaxPackageSize)
|
||||
{
|
||||
this.OnError(default, $"Received BodyLength={request.BodyLength}, greater than the set MaxPackageSize={this.MaxPackageSize}", true, true);
|
||||
await GoReceived(remoteEndPoint, null, request);
|
||||
}
|
||||
|
||||
if (request.BodyLength + request.HeadBytesLength > byteBlock.CanReadLength)
|
||||
{
|
||||
await GoReceived(remoteEndPoint, null, request);
|
||||
return;
|
||||
}
|
||||
if (request.BodyLength <= 0)
|
||||
if (request.BodyLength + request.HeaderLength > byteBlock.CanReadLength)
|
||||
{
|
||||
//如果body长度无法确定,直接读取全部
|
||||
request.BodyLength = byteBlock.Length;
|
||||
//body不满足解析,开始缓存,然后保存对象
|
||||
return;
|
||||
}
|
||||
|
||||
var result = UnpackResponse(request, byteBlock);
|
||||
|
||||
byteBlock.Position = byteBlock.Length;
|
||||
if (request.IsSuccess)
|
||||
//if (request.BodyLength <= 0)
|
||||
//{
|
||||
// //如果body长度无法确定,直接读取全部
|
||||
// request.BodyLength = byteBlock.Length;
|
||||
//}
|
||||
var headPos = pos + request.HeaderLength;
|
||||
byteBlock.Position = headPos;
|
||||
var result = request.CheckBody(ref byteBlock);
|
||||
if (result == FilterResult.Cache)
|
||||
{
|
||||
request.Content = result.Content;
|
||||
request.ReceivedBytes = byteBlock;
|
||||
if (Logger.LogLevel <= LogLevel.Trace)
|
||||
Logger.Trace($"{ToString()}-Received incomplete, cached message, current length:{byteBlock.Length} {request?.ErrorMessage}");
|
||||
request.OperCode = -1;
|
||||
}
|
||||
else if (result == FilterResult.GoOn)
|
||||
{
|
||||
if (byteBlock.Position == headPos)
|
||||
byteBlock.Position += 1;
|
||||
request.OperCode = -1;
|
||||
}
|
||||
else if (result == FilterResult.Success)
|
||||
{
|
||||
byteBlock.Position = request.HeaderLength + request.BodyLength + pos;
|
||||
await GoReceived(remoteEndPoint, null, request);
|
||||
}
|
||||
await GoReceived(remoteEndPoint, null, request);
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
byteBlock.Position = byteBlock.Length;//移动游标
|
||||
request.OperCode = -1;
|
||||
await GoReceived(remoteEndPoint, null, request);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -132,32 +140,35 @@ public abstract class ReadWriteDevicesUdpDataHandleAdapter<TRequest> : UdpDataHa
|
||||
{
|
||||
if (!(requestInfo is ISendMessage sendMessage))
|
||||
{
|
||||
throw new Exception($"Unable to convert {nameof(requestInfo)} to {nameof(SendMessage)}");
|
||||
throw new Exception($"Unable to convert {nameof(requestInfo)} to {nameof(ISendMessage)}");
|
||||
}
|
||||
var sendData = sendMessage.SendBytes;
|
||||
|
||||
if (Logger.LogLevel <= LogLevel.Trace)
|
||||
Logger?.Trace($"{ToString()}- Send:{(IsHexData ? sendData.Span.ToHexString() : (sendData.Span.ToString(Encoding.UTF8)))}");
|
||||
//非并发主从协议
|
||||
if (IsSingleThread)
|
||||
var requestInfoBuilder = (ISendMessage)requestInfo;
|
||||
|
||||
var byteBlock = new ValueByteBlock(requestInfoBuilder.MaxLength);
|
||||
try
|
||||
{
|
||||
SetRequest(sendMessage.Sign, sendData);
|
||||
requestInfoBuilder.Build(ref byteBlock);
|
||||
if (Logger.LogLevel <= LogLevel.Trace)
|
||||
Logger?.Trace($"{ToString()}- Send:{(IsHexData ? byteBlock.Span.ToHexString() : (byteBlock.Span.ToString(Encoding.UTF8)))}");
|
||||
//非并发主从协议
|
||||
if (IsSingleThread)
|
||||
{
|
||||
SetRequest(sendMessage.Sign, requestInfoBuilder);
|
||||
}
|
||||
await this.GoSendAsync(endPoint, byteBlock.Memory).ConfigureFalseAwait();
|
||||
}
|
||||
finally
|
||||
{
|
||||
byteBlock.SafeDispose();
|
||||
}
|
||||
|
||||
//发送
|
||||
await this.GoSendAsync(endPoint, sendData).ConfigureFalseAwait();
|
||||
}
|
||||
|
||||
public void SetRequest(int sign, ReadOnlyMemory<byte> sendData)
|
||||
public void SetRequest(int sign, ISendMessage sendMessage)
|
||||
{
|
||||
var request = GetInstance();
|
||||
request.Sign = sign;
|
||||
request.SendInfo(sendData);
|
||||
request.SendInfo(sendMessage);
|
||||
Request = request;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 解包获取实际数据包
|
||||
/// </summary>
|
||||
protected abstract AdapterResult UnpackResponse(TRequest request, IByteBlock byteBlock);
|
||||
}
|
||||
@@ -50,4 +50,7 @@ public enum DataTypeEnum
|
||||
|
||||
/// <inheritdoc/>
|
||||
Double,
|
||||
|
||||
/// <inheritdoc/>
|
||||
Decimal,
|
||||
}
|
||||
|
||||
@@ -62,6 +62,23 @@ public static class ByteExtensions
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 数组内容分别相加某个数字
|
||||
/// </summary>
|
||||
/// <param name="bytes"></param>
|
||||
/// <param name="value"></param>
|
||||
/// <returns></returns>
|
||||
public static ReadOnlySpan<byte> BytesAdd(this ReadOnlySpan<byte> bytes, int value)
|
||||
{
|
||||
byte[] result = new byte[bytes.Length];
|
||||
for (int index = 0; index < bytes.Length; index++)
|
||||
{
|
||||
result[index] = (byte)(bytes[index] + value);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取byte数据类型的第offset位,是否为True<br />
|
||||
/// </summary>
|
||||
|
||||
@@ -68,7 +68,7 @@ public static class DataTypeExtensions
|
||||
/// </summary>
|
||||
/// <param name="coreDataType"></param>
|
||||
/// <returns></returns>
|
||||
public static int GetByteLength(this DataTypeEnum coreDataType)
|
||||
public static byte GetByteLength(this DataTypeEnum coreDataType)
|
||||
{
|
||||
return coreDataType switch
|
||||
{
|
||||
@@ -82,6 +82,7 @@ public static class DataTypeExtensions
|
||||
DataTypeEnum.UInt64 => 8,
|
||||
DataTypeEnum.Single => 4,
|
||||
DataTypeEnum.Double => 8,
|
||||
DataTypeEnum.Decimal => 16,
|
||||
_ => 0,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -427,7 +427,7 @@ public interface IProtocol : IDisposable
|
||||
/// <param name="token">取消令箭</param>
|
||||
/// <param name="channel">通道</param>
|
||||
/// <returns>返回消息体</returns>
|
||||
ValueTask SendAsync(SendMessage sendMessage, IClientChannel channel = null, CancellationToken token = default);
|
||||
ValueTask<OperResult> SendAsync(ISendMessage sendMessage, IClientChannel channel = default, CancellationToken token = default);
|
||||
|
||||
/// <summary>
|
||||
/// 发送,会经过适配器,可传入socketId,如果为空,则默认通道必须为<see cref="IClientChannel"/>类型
|
||||
@@ -436,7 +436,7 @@ public interface IProtocol : IDisposable
|
||||
/// <param name="sendMessage">发送字节数组</param>
|
||||
/// <param name="cancellationToken">取消令箭</param>
|
||||
/// <returns>返回消息体</returns>
|
||||
ValueTask<OperResult> SendAsync(string socketId, SendMessage sendMessage, CancellationToken cancellationToken);
|
||||
ValueTask<OperResult> SendAsync(ISendMessage sendMessage, string socketId, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// 发送并等待返回,会经过适配器,可传入<see cref="IClientChannel"/>,如果为空,则默认通道必须为<see cref="IClientChannel"/>类型
|
||||
@@ -446,7 +446,7 @@ public interface IProtocol : IDisposable
|
||||
/// <param name="cancellationToken">取消令箭</param>
|
||||
/// <param name="channel">通道</param>
|
||||
/// <returns>返回消息体</returns>
|
||||
ValueTask<OperResult<byte[]>> SendThenReturnAsync(SendMessage command, WaitDataAsync<MessageBase> waitData = default, CancellationToken cancellationToken = default, IClientChannel channel = default);
|
||||
ValueTask<OperResult<byte[]>> SendThenReturnAsync(ISendMessage command, IClientChannel channel = default, WaitDataAsync<MessageBase> waitData = default, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// 发送并等待返回,会经过适配器,可传入socketId,如果为空,则默认通道必须为<see cref="IClientChannel"/>类型
|
||||
@@ -456,11 +456,7 @@ public interface IProtocol : IDisposable
|
||||
/// <param name="waitData">waitData</param>
|
||||
/// <param name="cancellationToken">取消令箭</param>
|
||||
/// <returns>返回消息体</returns>
|
||||
ValueTask<OperResult<byte[]>> SendThenReturnAsync(string socketId, SendMessage sendMessage, WaitDataAsync<MessageBase> waitData = default, CancellationToken cancellationToken = default);
|
||||
|
||||
ValueTask<OperResult<byte[]>> SendThenReturnAsync(string socketId, ReadOnlyMemory<byte> sendBytes, CancellationToken cancellationToken = default);
|
||||
|
||||
ValueTask<OperResult<byte[]>> SendThenReturnAsync(ReadOnlyMemory<byte> sendBytes, CancellationToken cancellationToken = default);
|
||||
ValueTask<OperResult<byte[]>> SendThenReturnAsync(ISendMessage sendMessage, string socketId, WaitDataAsync<MessageBase> waitData = default, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// 布尔量解析时是否需要按字反转
|
||||
|
||||
@@ -235,57 +235,49 @@ public abstract class ProtocolBase : DisposableObject, IProtocol
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual async ValueTask SendAsync(SendMessage sendMessage, IClientChannel channel = default, CancellationToken token = default)
|
||||
public virtual async ValueTask<OperResult> SendAsync(ISendMessage sendMessage, IClientChannel channel = default, CancellationToken token = default)
|
||||
{
|
||||
if (!Channel.Online)
|
||||
await Channel.ConnectAsync(ConnectTimeout, token).ConfigureAwait(false);
|
||||
if (SendDelayTime != 0)
|
||||
await Task.Delay(SendDelayTime, token).ConfigureAwait(false);
|
||||
|
||||
if (token.IsCancellationRequested)
|
||||
return;
|
||||
|
||||
if (channel == default)
|
||||
try
|
||||
{
|
||||
if (Channel is not IClientChannel clientChannel) { throw new ArgumentNullException(nameof(channel)); }
|
||||
await clientChannel.SendAsync(sendMessage).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
await channel.SendAsync(sendMessage).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
if (!Channel.Online)
|
||||
await Channel.ConnectAsync(ConnectTimeout, token).ConfigureAwait(false);
|
||||
if (SendDelayTime != 0)
|
||||
await Task.Delay(SendDelayTime, token).ConfigureAwait(false);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual async ValueTask<OperResult> SendAsync(string socketId, SendMessage sendMessage, CancellationToken cancellationToken)
|
||||
{
|
||||
if (Channel.ChannelType == ChannelTypeEnum.TcpService)
|
||||
{
|
||||
if (((TcpServiceChannel)Channel).Clients.TryGetClient($"ID={socketId}", out TcpSessionClientChannel? client))
|
||||
if (token.IsCancellationRequested)
|
||||
return new(new OperationCanceledException());
|
||||
|
||||
if (channel == default)
|
||||
{
|
||||
await SendAsync(sendMessage, client);
|
||||
return OperResult.Success;
|
||||
if (Channel is not IClientChannel clientChannel) { throw new ArgumentNullException(nameof(channel)); }
|
||||
await clientChannel.SendAsync(sendMessage).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
return new OperResult(DefaultResource.Localizer["DtuNoConnectedWaining"]);
|
||||
}
|
||||
else
|
||||
{
|
||||
await SendAsync(sendMessage);
|
||||
{
|
||||
await channel.SendAsync(sendMessage).ConfigureAwait(false);
|
||||
}
|
||||
return OperResult.Success;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new(ex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual ValueTask<OperResult<byte[]>> SendThenReturnAsync(ReadOnlyMemory<byte> sendBytes, CancellationToken cancellationToken = default)
|
||||
public virtual async ValueTask<OperResult> SendAsync(ISendMessage sendMessage, string socketId, CancellationToken cancellationToken)
|
||||
{
|
||||
return SendThenReturnAsync(new SendMessage(sendBytes), default, cancellationToken);
|
||||
}
|
||||
try
|
||||
{
|
||||
var channelResult = GetChannel(socketId);
|
||||
if (!channelResult.IsSuccess) return new OperResult<byte[]>(channelResult);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual ValueTask<OperResult<byte[]>> SendThenReturnAsync(string socketId, ReadOnlyMemory<byte> sendBytes, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return SendThenReturnAsync(socketId, new SendMessage(sendBytes), default, cancellationToken);
|
||||
return await SendAsync(sendMessage, channelResult.Content, cancellationToken);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new(ex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
@@ -303,15 +295,15 @@ public abstract class ProtocolBase : DisposableObject, IProtocol
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual async ValueTask<OperResult<byte[]>> SendThenReturnAsync(string socketId, SendMessage sendMessage, WaitDataAsync<MessageBase> waitData = default, CancellationToken cancellationToken = default)
|
||||
public virtual async ValueTask<OperResult<byte[]>> SendThenReturnAsync(ISendMessage sendMessage, string socketId, WaitDataAsync<MessageBase> waitData = default, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var channelResult = GetChannel(socketId);
|
||||
if (!channelResult.IsSuccess) return new OperResult<byte[]>(channelResult);
|
||||
return await SendThenReturnAsync(sendMessage, waitData, cancellationToken, channelResult.Content);
|
||||
return await SendThenReturnAsync(sendMessage, channelResult.Content, waitData, cancellationToken);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual async ValueTask<OperResult<byte[]>> SendThenReturnAsync(SendMessage command, WaitDataAsync<MessageBase> waitData = default, CancellationToken cancellationToken = default, IClientChannel channel = default)
|
||||
public virtual async ValueTask<OperResult<byte[]>> SendThenReturnAsync(ISendMessage command, IClientChannel channel = default, WaitDataAsync<MessageBase> waitData = default, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (!Channel.Online)
|
||||
await Channel.ConnectAsync(ConnectTimeout, cancellationToken).ConfigureAwait(false);
|
||||
@@ -329,7 +321,7 @@ public abstract class ProtocolBase : DisposableObject, IProtocol
|
||||
waitData = clientChannel.WaitHandlePool.GetWaitDataAsync(out var sign);
|
||||
command.Sign = sign;
|
||||
}
|
||||
result = await GetResponsedDataAsync(command, Timeout, clientChannel, waitData, cancellationToken).ConfigureAwait(false);
|
||||
result = await GetResponsedDataAsync(command, clientChannel, waitData, Timeout, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -338,7 +330,7 @@ public abstract class ProtocolBase : DisposableObject, IProtocol
|
||||
waitData = channel.WaitHandlePool.GetWaitDataAsync(out var sign);
|
||||
command.Sign = sign;
|
||||
}
|
||||
result = await GetResponsedDataAsync(command, Timeout, channel, waitData, cancellationToken).ConfigureAwait(false);
|
||||
result = await GetResponsedDataAsync(command, channel, waitData, Timeout, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
return new OperResult<byte[]>(result) { Content = result.Content };
|
||||
@@ -350,7 +342,7 @@ public abstract class ProtocolBase : DisposableObject, IProtocol
|
||||
/// <summary>
|
||||
/// 发送并等待数据
|
||||
/// </summary>
|
||||
protected virtual async ValueTask<MessageBase> GetResponsedDataAsync(SendMessage command, int timeout, IClientChannel clientChannel, WaitDataAsync<MessageBase> waitData = default, CancellationToken cancellationToken = default)
|
||||
protected virtual async ValueTask<MessageBase> GetResponsedDataAsync(ISendMessage command, IClientChannel clientChannel, WaitDataAsync<MessageBase> waitData = default, int timeout = 3000, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (waitData == default)
|
||||
{
|
||||
@@ -372,7 +364,7 @@ public abstract class ProtocolBase : DisposableObject, IProtocol
|
||||
}
|
||||
else
|
||||
{
|
||||
return new MessageBase(result);
|
||||
throw new Exception(result.ErrorMessage, result.Exception);
|
||||
}
|
||||
}
|
||||
finally
|
||||
|
||||
@@ -11,8 +11,8 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.Localization.Abstractions" Version="8.0.6" />
|
||||
<PackageReference Include="TouchSocket" Version="2.1.0-alpha.21" />
|
||||
<PackageReference Include="TouchSocket.SerialPorts" Version="2.1.0-alpha.21" />
|
||||
<PackageReference Include="TouchSocket" Version="2.1.0-alpha.22" />
|
||||
<PackageReference Include="TouchSocket.SerialPorts" Version="2.1.0-alpha.22" />
|
||||
<ProjectReference Include="$(SolutionDir)\ThingsGateway.NewLife.X\ThingsGateway.NewLife.X.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
@@ -18,31 +18,27 @@ public class CRC16Utils
|
||||
/// <summary>
|
||||
/// 通过指定多项式码来获取对应的数据的CRC校验码
|
||||
/// </summary>
|
||||
/// <param name="memory">需要校验的数据,不包含CRC字节</param>
|
||||
/// <param name="bytes">需要校验的数据,不包含CRC字节</param>
|
||||
/// <returns>返回带CRC校验码的字节数组,可用于串口发送</returns>
|
||||
public static byte[] Crc16Only(ReadOnlyMemory<byte> memory)
|
||||
public static byte[] Crc16Only(ReadOnlySpan<byte> bytes)
|
||||
{
|
||||
return Crc16Only(memory, 0xA001);
|
||||
return Crc16Only(bytes, 0xA001);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 通过指定多项式码来获取对应的数据的CRC校验码
|
||||
/// </summary>
|
||||
/// <param name="memory">需要校验的数据,不包含CRC字节</param>
|
||||
/// <param name="bytes">需要校验的数据,不包含CRC字节</param>
|
||||
/// <param name="xdapoly">多项式</param>
|
||||
/// <param name="crc16">crc16</param>
|
||||
/// <returns>返回带CRC校验码的字节数组,可用于串口发送</returns>
|
||||
public static byte[] Crc16Only(ReadOnlyMemory<byte> memory, int xdapoly, bool crc16 = true)
|
||||
public static byte[] Crc16Only(ReadOnlySpan<byte> bytes, int xdapoly, bool crc16 = true)
|
||||
{
|
||||
var memoryArray = memory.GetArray();
|
||||
|
||||
byte[] data = memoryArray.Array;
|
||||
int offset = memoryArray.Offset;
|
||||
int length = memoryArray.Count;
|
||||
int length = bytes.Length;
|
||||
int num = 0xFFFF;
|
||||
for (int i = offset; i < length; i++)
|
||||
for (int i = 0; i < length; i++)
|
||||
{
|
||||
num = (num >> ((!crc16) ? 8 : 0)) ^ data[i];
|
||||
num = (num >> ((!crc16) ? 8 : 0)) ^ bytes[i];
|
||||
for (int j = 0; j < 8; j++)
|
||||
{
|
||||
int num2 = num & 1;
|
||||
@@ -54,14 +50,14 @@ public class CRC16Utils
|
||||
}
|
||||
}
|
||||
|
||||
return (!crc16) ? new byte[2]
|
||||
{
|
||||
return (!crc16) ?
|
||||
[
|
||||
(byte)(num >> 8),
|
||||
(byte)((uint)num & 0xFFu)
|
||||
} : new byte[2]
|
||||
{
|
||||
] :
|
||||
[
|
||||
(byte)((uint)num & 0xFFu),
|
||||
(byte)(num >> 8)
|
||||
};
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<PackageReference Include="SqlSugar.TDengineCore" Version="4.18.0" />
|
||||
<PackageReference Include="Rougamo.Fody" Version="3.0.0" />
|
||||
<PackageReference Include="CS-Script" Version="4.8.16" />
|
||||
<PackageReference Include="TouchSocket.Dmtp" Version="2.1.0-alpha.21" />
|
||||
<PackageReference Include="TouchSocket.Dmtp" Version="2.1.0-alpha.22" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -134,7 +134,7 @@ public static class Pool
|
||||
{
|
||||
//if (ms == null) return null;
|
||||
|
||||
var buf = requireResult ? ms.ToArray() : new Byte[0];
|
||||
var buf = requireResult ? ms.ToArray() : [];
|
||||
|
||||
Pool.MemoryStream.Put(ms);
|
||||
|
||||
@@ -149,7 +149,7 @@ public static class Pool
|
||||
{
|
||||
//if (ms == null) return null;
|
||||
|
||||
var buf = returnResult ? ms.ToArray() : new Byte[0];
|
||||
var buf = returnResult ? ms.ToArray() : [];
|
||||
|
||||
Pool.MemoryStream.Put(ms);
|
||||
|
||||
|
||||
@@ -71,7 +71,7 @@ public class QueueService<T> : IQueueService<T>
|
||||
foreach (var item in clients)
|
||||
{
|
||||
var queue = item.Value;
|
||||
rs += queue.Add(new[] { value });
|
||||
rs += queue.Add([value]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ public class PinYin
|
||||
{
|
||||
#region 数组信息
|
||||
|
||||
private static readonly Int32[] pyValue = new[] {
|
||||
private static readonly Int32[] pyValue = [
|
||||
-20319, -20317, -20304, -20295, -20292, -20283, -20265, -20257, -20242,
|
||||
-20230, -20051, -20036, -20032, -20026, -20002, -19990, -19986, -19982,
|
||||
-19976, -19805, -19784, -19775, -19774, -19763, -19756, -19751, -19746,
|
||||
@@ -65,9 +65,9 @@ public class PinYin
|
||||
-11014, -10838, -10832, -10815, -10800, -10790, -10780, -10764, -10587,
|
||||
-10544, -10533, -10519, -10331, -10329, -10328, -10322, -10315, -10309,
|
||||
-10307, -10296, -10281, -10274, -10270, -10262, -10260, -10256, -10254
|
||||
};
|
||||
];
|
||||
|
||||
private static readonly String[] pyName = new[] {
|
||||
private static readonly String[] pyName = [
|
||||
"A", "Ai", "An", "Ang", "Ao", "Ba", "Bai", "Ban", "Bang", "Bao", "Bei",
|
||||
"Ben", "Beng", "Bi", "Bian", "Biao", "Bie", "Bin", "Bing", "Bo", "Bu",
|
||||
"Ba", "Cai", "Can", "Cang", "Cao", "Ce", "Ceng", "Cha", "Chai", "Chan",
|
||||
@@ -108,14 +108,14 @@ public class PinYin
|
||||
"Zheng", "Zhi", "Zhong", "Zhou", "Zhu", "Zhua", "Zhuai", "Zhuan",
|
||||
"Zhuang", "Zhui", "Zhun", "Zhuo", "Zi", "Zong", "Zou", "Zu", "Zuan",
|
||||
"Zui", "Zun", "Zuo"
|
||||
};
|
||||
];
|
||||
|
||||
#endregion 数组信息
|
||||
|
||||
#region 二级汉字
|
||||
|
||||
/// <summary>二级汉字数组</summary>
|
||||
private static readonly Char[] _py2 = new[] {
|
||||
private static readonly Char[] _py2 = [
|
||||
'亍','丌','兀','丐','廿','卅','丕','亘','丞','鬲','孬','噩','丨','禺','丿'
|
||||
,'匕','乇','夭','爻','卮','氐','囟','胤','馗','毓','睾','鼗','丶','亟','鼐','乜'
|
||||
,'乩','亓','芈','孛','啬','嘏','仄','厍','厝','厣','厥','厮','靥','赝','匚','叵'
|
||||
@@ -308,10 +308,10 @@ public class PinYin
|
||||
,'餍','餮','饕','饔','髟','髡','髦','髯','髫','髻','髭','髹','鬈','鬏','鬓','鬟'
|
||||
,'鬣','麽','麾','縻','麂','麇','麈','麋','麒','鏖','麝','麟','黛','黜','黝','黠'
|
||||
,'黟','黢','黩','黧','黥','黪','黯','鼢','鼬','鼯','鼹','鼷','鼽','鼾','齄'
|
||||
};
|
||||
];
|
||||
|
||||
/// <summary>二级汉字对应拼音数组</summary>
|
||||
private static readonly String[] _pyValue2 = new[] {
|
||||
private static readonly String[] _pyValue2 = [
|
||||
"chu","ji","wu","gai","nian","sa","pi","gen","cheng","ge","nao","e","shu","yu","pie"
|
||||
,"bi","tuo","yao","yao","zhi","di","xin","yin","kui","yu","gao","tao","dian","ji","nai","nie"
|
||||
,"ji","qi","mi","bei","se","gu","ze","she","cuo","yan","jue","si","ye","yan","fang","po"
|
||||
@@ -504,21 +504,21 @@ public class PinYin
|
||||
,"yan","tie","tao","yong","biao","kun","mao","ran","tiao","ji","zi","xiu","quan","jiu","bin","huan"
|
||||
,"lie","me","hui","mi","ji","jun","zhu","mi","qi","ao","she","lin","dai","chu","you","xia"
|
||||
,"yi","qu","du","li","qing","can","an","fen","you","wu","yan","xi","qiu","han","zha"
|
||||
};
|
||||
];
|
||||
|
||||
#endregion 二级汉字
|
||||
|
||||
#region 三级汉字
|
||||
|
||||
private static readonly Char[] _py3 = new[] {
|
||||
private static readonly Char[] _py3 = [
|
||||
'沚', '埇', '瀍', '浉', '猇', '鄠', '崁', '埗', '漷', '甪',
|
||||
'滘', '垱'
|
||||
};
|
||||
];
|
||||
|
||||
private static readonly String[] _pyValue3 = new[] {
|
||||
private static readonly String[] _pyValue3 = [
|
||||
"Zhi", "Yong", "Chan", "Shi", "Xiao", "Hu", "Kan", "Bu", "Huo", "Lu",
|
||||
"Jiao", "Dang"
|
||||
};
|
||||
];
|
||||
|
||||
#endregion 三级汉字
|
||||
|
||||
@@ -656,7 +656,7 @@ public class PinYin
|
||||
/// <returns>转换后的拼音(全拼)字符串</returns>
|
||||
public static String[] GetAll(String str)
|
||||
{
|
||||
if (str.IsNullOrEmpty()) return new String[0];
|
||||
if (str.IsNullOrEmpty()) return [];
|
||||
|
||||
// 重点地区支持
|
||||
if (str == "重庆") return ["Chong", "Qing"];
|
||||
|
||||
@@ -60,8 +60,8 @@ internal class SpeakProvider
|
||||
{
|
||||
try
|
||||
{
|
||||
synth = _type.CreateInstance(new Object[0]);
|
||||
synth?.Invoke("SetOutputToDefaultAudioDevice", new Object[0]);
|
||||
synth = _type.CreateInstance([]);
|
||||
synth?.Invoke("SetOutputToDefaultAudioDevice", []);
|
||||
}
|
||||
catch
|
||||
{
|
||||
|
||||
@@ -95,7 +95,7 @@ public static class StringHelper
|
||||
public static String[] Split(this String? value, params String[] separators)
|
||||
{
|
||||
//!! netcore3.0中新增Split(String? separator, StringSplitOptions options = StringSplitOptions.None),优先于StringHelper扩展
|
||||
if (value == null || String.IsNullOrEmpty(value)) return new String[0];
|
||||
if (value == null || String.IsNullOrEmpty(value)) return [];
|
||||
if (separators == null || separators.Length <= 0 || separators.Length == 1 && separators[0].IsNullOrEmpty()) separators = [",", ";"];
|
||||
|
||||
return value.Split(separators, StringSplitOptions.RemoveEmptyEntries);
|
||||
@@ -108,7 +108,7 @@ public static class StringHelper
|
||||
/// <returns></returns>
|
||||
public static Int32[] SplitAsInt(this String? value, params String[] separators)
|
||||
{
|
||||
if (value == null || String.IsNullOrEmpty(value)) return new Int32[0];
|
||||
if (value == null || String.IsNullOrEmpty(value)) return [];
|
||||
if (separators == null || separators.Length <= 0) separators = [",", ";"];
|
||||
|
||||
var ss = value.Split(separators, StringSplitOptions.RemoveEmptyEntries);
|
||||
@@ -291,7 +291,7 @@ public static class StringHelper
|
||||
public static Byte[] GetBytes(this String? value, Encoding? encoding = null)
|
||||
{
|
||||
//if (value == null) return null;
|
||||
if (String.IsNullOrEmpty(value)) return new Byte[0];
|
||||
if (String.IsNullOrEmpty(value)) return [];
|
||||
|
||||
encoding ??= Encoding.UTF8;
|
||||
return encoding.GetBytes(value);
|
||||
@@ -604,7 +604,7 @@ public static class StringHelper
|
||||
/// <returns></returns>
|
||||
public static String[] LevenshteinSearch(String key, String[] words)
|
||||
{
|
||||
if (IsNullOrWhiteSpace(key)) return new String[0];
|
||||
if (IsNullOrWhiteSpace(key)) return [];
|
||||
|
||||
var keys = key.Split(new Char[] { ' ', ' ' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
@@ -677,7 +677,7 @@ public static class StringHelper
|
||||
/// <returns></returns>
|
||||
public static String[] LCSSearch(String key, String[] words)
|
||||
{
|
||||
if (IsNullOrWhiteSpace(key) || words == null || words.Length == 0) return new String[0];
|
||||
if (IsNullOrWhiteSpace(key) || words == null || words.Length == 0) return [];
|
||||
|
||||
var keys = key
|
||||
.Split(new Char[] { ' ', '\u3000' }, StringSplitOptions.RemoveEmptyEntries)
|
||||
|
||||
@@ -198,7 +198,7 @@ public static class IOHelper
|
||||
public static Byte[] ReadArray(this Stream des)
|
||||
{
|
||||
var len = des.ReadEncodedInt();
|
||||
if (len <= 0) return new Byte[0];
|
||||
if (len <= 0) return [];
|
||||
|
||||
// 避免数据错乱超长
|
||||
//if (des.CanSeek && len > des.Length - des.Position) len = (Int32)(des.Length - des.Position);
|
||||
@@ -242,7 +242,7 @@ public static class IOHelper
|
||||
/// <returns>返回复制的总字节数</returns>
|
||||
public static Byte[] ReadBytes(this Byte[] src, Int32 offset, Int32 count)
|
||||
{
|
||||
if (count == 0) return new Byte[0];
|
||||
if (count == 0) return [];
|
||||
|
||||
// 即使是全部,也要复制一份,而不只是返回原数组,因为可能就是为了复制数组
|
||||
if (count < 0) count = src.Length - offset;
|
||||
@@ -284,7 +284,7 @@ public static class IOHelper
|
||||
public static Byte[] ReadBytes(this Stream stream, Int64 length)
|
||||
{
|
||||
//if (stream == null) return null;
|
||||
if (length == 0) return new Byte[0];
|
||||
if (length == 0) return [];
|
||||
|
||||
if (length > 0 && stream.CanSeek && stream.Length - stream.Position < length)
|
||||
throw new XException("Unable to read {1} bytes of data from a data stream with a length of only {0}", stream.Length - stream.Position, length);
|
||||
@@ -844,7 +844,7 @@ public static class IOHelper
|
||||
/// <returns></returns>
|
||||
public static Byte[] ToHex(this String? data, Int32 startIndex = 0, Int32 length = -1)
|
||||
{
|
||||
if (data.IsNullOrEmpty()) return new Byte[0];
|
||||
if (data.IsNullOrEmpty()) return [];
|
||||
|
||||
// 过滤特殊字符
|
||||
data = data.Trim()
|
||||
@@ -906,7 +906,7 @@ public static class IOHelper
|
||||
/// <returns></returns>
|
||||
public static Byte[] ToBase64(this String? data)
|
||||
{
|
||||
if (data.IsNullOrWhiteSpace()) return new Byte[0];
|
||||
if (data.IsNullOrWhiteSpace()) return [];
|
||||
|
||||
data = data.Trim();
|
||||
if (data[^1] != '=')
|
||||
|
||||
@@ -461,7 +461,7 @@ public static class PathHelper
|
||||
/// <returns></returns>
|
||||
public static String[] CopyTo(this DirectoryInfo di, String destDirName, String? exts = null, Boolean allSub = false, Action<String>? callback = null)
|
||||
{
|
||||
if (!di.Exists) return new String[0];
|
||||
if (!di.Exists) return [];
|
||||
|
||||
var list = new List<String>();
|
||||
|
||||
@@ -490,7 +490,7 @@ public static class PathHelper
|
||||
public static String[] CopyToIfNewer(this DirectoryInfo di, String destDirName, String? exts = null, Boolean allSub = false, Action<String>? callback = null)
|
||||
{
|
||||
var dest = destDirName.AsDirectory();
|
||||
if (!dest.Exists) return new String[0];
|
||||
if (!dest.Exists) return [];
|
||||
|
||||
var list = new List<String>();
|
||||
|
||||
|
||||
@@ -29,14 +29,14 @@ public static class AttributeX
|
||||
/// <returns></returns>
|
||||
public static TAttribute[] GetCustomAttributes<TAttribute>(this Assembly assembly)
|
||||
{
|
||||
if (assembly == null) return new TAttribute[0];
|
||||
if (assembly == null) return [];
|
||||
|
||||
var key = $"{assembly.FullName}_{typeof(TAttribute).FullName}";
|
||||
|
||||
return (TAttribute[])_asmCache.GetOrAdd(key, k =>
|
||||
{
|
||||
var atts = assembly.GetCustomAttributes(typeof(TAttribute), true) as TAttribute[];
|
||||
return atts ?? (new TAttribute[0]);
|
||||
return atts ?? ([]);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -82,7 +82,7 @@ public static class Reflect
|
||||
/// <returns></returns>
|
||||
public static MethodInfo[] GetMethodsEx(this Type type, String name, Int32 paramCount = -1)
|
||||
{
|
||||
if (name.IsNullOrEmpty()) return new MethodInfo[0];
|
||||
if (name.IsNullOrEmpty()) return [];
|
||||
|
||||
return Provider.GetMethods(type, name, paramCount);
|
||||
}
|
||||
|
||||
@@ -129,7 +129,7 @@ public sealed class CbcTransform : ICryptoTransform
|
||||
/// <exception cref="ArgumentException"></exception>
|
||||
public Byte[] TransformFinalBlock(Byte[] inputBuffer, Int32 inputOffset, Int32 inputCount)
|
||||
{
|
||||
if (inputCount == 0) return new Byte[0];
|
||||
if (inputCount == 0) return [];
|
||||
|
||||
var blocks = inputCount / InputBlockSize;
|
||||
var output = new Byte[blocks * OutputBlockSize];
|
||||
|
||||
@@ -126,7 +126,7 @@ public sealed class PKCS7PaddingTransform : ICryptoTransform
|
||||
/// <returns></returns>
|
||||
public Byte[] TransformFinalBlock(Byte[] inputBuffer, Int32 inputOffset, Int32 inputCount)
|
||||
{
|
||||
if (inputCount == 0) return new Byte[0];
|
||||
if (inputCount == 0) return [];
|
||||
|
||||
if (_encryptMode)
|
||||
{
|
||||
|
||||
@@ -27,7 +27,7 @@ internal class RC4
|
||||
/// <returns></returns>
|
||||
public static Byte[] Encrypt(Byte[] data, Byte[] pass)
|
||||
{
|
||||
if (data == null || data.Length == 0) return new Byte[0];
|
||||
if (data == null || data.Length == 0) return [];
|
||||
if (pass == null || pass.Length == 0) return data;
|
||||
|
||||
var output = new Byte[data.Length];
|
||||
|
||||
@@ -29,8 +29,8 @@ public class SM4 : SymmetricAlgorithm
|
||||
KeySizeValue = 128;
|
||||
BlockSizeValue = 128;
|
||||
FeedbackSizeValue = BlockSizeValue;
|
||||
LegalBlockSizesValue = new[] { new KeySizes(128, 128, 0) };
|
||||
LegalKeySizesValue = new[] { new KeySizes(128, 128, 0) };
|
||||
LegalBlockSizesValue = [new KeySizes(128, 128, 0)];
|
||||
LegalKeySizesValue = [new KeySizes(128, 128, 0)];
|
||||
|
||||
Mode = CipherMode.ECB;
|
||||
Padding = PaddingMode.PKCS7;
|
||||
@@ -337,7 +337,7 @@ public class SM4Transform : ICryptoTransform
|
||||
/// <exception cref="ArgumentException"></exception>
|
||||
public Byte[] TransformFinalBlock(Byte[] inputBuffer, Int32 inputOffset, Int32 inputCount)
|
||||
{
|
||||
if (inputCount == 0) return new Byte[0];
|
||||
if (inputCount == 0) return [];
|
||||
|
||||
var blocks = inputCount / InputBlockSize;
|
||||
var output = new Byte[blocks * OutputBlockSize];
|
||||
|
||||
@@ -84,7 +84,7 @@ public sealed class ZerosPaddingTransform : ICryptoTransform
|
||||
/// <returns></returns>
|
||||
public Byte[] TransformFinalBlock(Byte[] inputBuffer, Int32 inputOffset, Int32 inputCount)
|
||||
{
|
||||
if (inputCount == 0) return new Byte[0];
|
||||
if (inputCount == 0) return [];
|
||||
|
||||
//todo !!! 仅能临时解决短密文填充清理问题
|
||||
if (_encryptMode && inputCount % InputBlockSize != 0)
|
||||
|
||||
@@ -162,7 +162,7 @@ public class Cron
|
||||
}
|
||||
|
||||
var rs = new List<Int32>();
|
||||
vs = new Int32[0];
|
||||
vs = [];
|
||||
|
||||
// 递归处理混合值
|
||||
if (value.Contains(','))
|
||||
|
||||
@@ -71,7 +71,7 @@ public class TimerScheduler
|
||||
private Thread? thread;
|
||||
private Int32 _tid;
|
||||
|
||||
private TimerX[] Timers = new TimerX[0];
|
||||
private TimerX[] Timers = [];
|
||||
|
||||
#endregion 属性
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="BootstrapBlazor" Version="8.6.3" />
|
||||
<PackageReference Include="BootstrapBlazor" Version="8.6.4" />
|
||||
<PackageReference Include="BootstrapBlazor.FontAwesome" Version="8.0.4" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ public abstract class AppAuthorizeHandler : IAuthorizationHandler
|
||||
/// <summary>
|
||||
/// 刷新 Token 身份标识
|
||||
/// </summary>
|
||||
private readonly string[] _refreshTokenClaims = new[] { "f", "e", "s", "l", "k" };
|
||||
private readonly string[] _refreshTokenClaims = ["f", "e", "s", "l", "k"];
|
||||
|
||||
/// <summary>
|
||||
/// 授权验证核心方法
|
||||
|
||||
@@ -41,10 +41,10 @@ public class SingleFilePublish : ISingleFilePublish
|
||||
{
|
||||
#if !Admin
|
||||
InstanceFactory.CustomAssemblies =
|
||||
new System.Reflection.Assembly[] { typeof(SqlSugar.TDengine.TDengineProvider).Assembly };
|
||||
[typeof(SqlSugar.TDengine.TDengineProvider).Assembly];
|
||||
#endif
|
||||
return new[]
|
||||
{
|
||||
return
|
||||
[
|
||||
"ThingsGateway.Foundation",
|
||||
"ThingsGateway.Core",
|
||||
"ThingsGateway.NewLife.X",
|
||||
@@ -54,6 +54,6 @@ public class SingleFilePublish : ISingleFilePublish
|
||||
"ThingsGateway.Admin.Razor" ,
|
||||
"ThingsGateway.Admin.Application" ,
|
||||
"SqlSugar.TDengineCore",
|
||||
};
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<Version>6.0.3.47</Version>
|
||||
<Version>6.0.3.50</Version>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -145,7 +145,7 @@ internal class ModbusMasterTest
|
||||
//源生成WriteData1与WriteData2方法(Write{属性名称})
|
||||
await modbusVariable.WriteData3Async("123", default);
|
||||
await modbusVariable.WriteData2Async(1, default);
|
||||
await modbusVariable.WriteData1Async(new ushort[] { 1, 2 }, default);
|
||||
await modbusVariable.WriteData1Async([1, 2], default);
|
||||
|
||||
//执行连读
|
||||
await modbusVariable.MultiReadAsync();
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
<PackageReference Include="ThingsGateway.Foundation.SiemensS7" Version="6.0.3.47" />
|
||||
<PackageReference Include="ThingsGateway.Foundation.Variable" Version="8.6.0" />
|
||||
<PackageReference Include="ThingsGateway.Foundation.Modbus" Version="6.0.3.47" />
|
||||
<PackageReference Include="TouchSocket.Modbus" Version="2.1.0-alpha.21" />
|
||||
<PackageReference Include="TouchSocket.Modbus" Version="2.1.0-alpha.22" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://kimdiego2098.github.io/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using TouchSocket.Core;
|
||||
|
||||
namespace ThingsGateway.Foundation.Dlt645;
|
||||
|
||||
/// <summary>
|
||||
/// Dlt645_2007DataHandleAdapter
|
||||
/// </summary>
|
||||
internal class Dlt645_2007DataHandleAdapter : ReadWriteDevicesSingleStreamDataHandleAdapter<Dlt645_2007Message>
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
protected override AdapterResult UnpackResponse(Dlt645_2007Message request, IByteBlock byteBlock)
|
||||
{
|
||||
return Dlt645Helper.GetResponse(request, byteBlock);
|
||||
}
|
||||
}
|
||||
@@ -8,29 +8,69 @@
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using TouchSocket.Core;
|
||||
|
||||
namespace ThingsGateway.Foundation.Dlt645;
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc/>
|
||||
/// </summary>
|
||||
internal class Dlt645_2007Response : Dlt645_2007Request
|
||||
{
|
||||
/// <summary>
|
||||
/// 错误码
|
||||
/// </summary>
|
||||
public byte? ErrorCode { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc/>
|
||||
/// </summary>
|
||||
internal class Dlt645_2007Message : MessageBase, IResultMessage
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public override int HeadBytesLength => 0;
|
||||
public override int HeaderLength { get; set; } = 10;
|
||||
|
||||
public ReadOnlyMemory<byte> SendBytes { get; set; }
|
||||
public Dlt645_2007Address? Request { get; set; }
|
||||
public Dlt645_2007Send? Dlt645_2007Send { get; set; }
|
||||
|
||||
public override void SendInfo(ReadOnlyMemory<byte> sendBytes)
|
||||
public Dlt645_2007Response Response { get; set; } = new();
|
||||
|
||||
public override void SendInfo(ISendMessage sendMessage)
|
||||
{
|
||||
SendBytes = sendBytes;
|
||||
Dlt645_2007Send = (sendMessage as Dlt645_2007Send);
|
||||
Request = Dlt645_2007Send.Dlt645_2007Address;
|
||||
}
|
||||
|
||||
private int HeadCodeIndex;
|
||||
private readonly byte[] ReadStation = [0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA];
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool CheckHeadBytes(byte[]? headBytes)
|
||||
public override bool CheckHead<TByteBlock>(ref TByteBlock byteBlock)
|
||||
{
|
||||
if (SendBytes.Length > 0)
|
||||
if (Request != null)
|
||||
{
|
||||
BodyLength = 0;
|
||||
//因为设备可能带有FE前导符开头,这里找到0x68的位置
|
||||
|
||||
if (byteBlock != null)
|
||||
{
|
||||
for (int index = byteBlock.Position; index < byteBlock.Length; index++)
|
||||
{
|
||||
if (byteBlock[index] == 0x68)
|
||||
{
|
||||
HeadCodeIndex = index;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//帧起始符 地址域 帧起始符 控制码 数据域长度共10个字节
|
||||
HeaderLength = HeadCodeIndex - byteBlock.Position + 10;
|
||||
if (byteBlock.CanReadLength < HeaderLength + HeadCodeIndex)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
BodyLength = byteBlock[HeadCodeIndex + 9] + 2;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
@@ -38,4 +78,70 @@ internal class Dlt645_2007Message : MessageBase, IResultMessage
|
||||
return false;//不是主动请求的,可能是心跳DTU包,直接放弃
|
||||
}
|
||||
}
|
||||
|
||||
public override FilterResult CheckBody<TByteBlock>(ref TByteBlock byteBlock)
|
||||
{
|
||||
int sendHeadCodeIndex = Dlt645_2007Send.SendHeadCodeIndex;
|
||||
|
||||
var pos = byteBlock.Position - HeaderLength;
|
||||
var endIndex = HeaderLength + BodyLength;
|
||||
if (byteBlock[endIndex - 1] == 0x16)
|
||||
{
|
||||
//检查校验码
|
||||
int sumCheck = 0;
|
||||
for (int i = HeadCodeIndex; i < endIndex - 2; i++)
|
||||
sumCheck += byteBlock[i];
|
||||
if ((byte)sumCheck != byteBlock[endIndex - 2])
|
||||
{
|
||||
//校验错误
|
||||
this.ErrorMessage = DltResource.Localizer["SumError"];
|
||||
this.OperCode = 999;
|
||||
return FilterResult.Success;
|
||||
}
|
||||
Response.Station = byteBlock.Span.Slice(HeadCodeIndex + 1, 6).ToArray();
|
||||
if (!Response.Station.SequenceEqual(Request.Station))//设备地址不符合时,返回错误
|
||||
{
|
||||
if (!Request.Station.SequenceEqual(ReadStation))//读写通讯地址例外
|
||||
{
|
||||
this.ErrorMessage = DltResource.Localizer["StationNotSame"];
|
||||
this.OperCode = 999;
|
||||
return FilterResult.Success;
|
||||
}
|
||||
}
|
||||
|
||||
var controlCode = byteBlock[HeadCodeIndex + 8];
|
||||
if ((controlCode & 0x40) == 0x40)//控制码bit6为1时,返回错误
|
||||
{
|
||||
Response.ErrorCode = (byte)(byteBlock[HeadCodeIndex + 10] - 0x33);
|
||||
var error = Dlt645Helper.Get2007ErrorMessage(Response.ErrorCode.Value);
|
||||
this.ErrorMessage = DltResource.Localizer["FunctionError", $"0x{controlCode:X2}", error];
|
||||
this.OperCode = 999;
|
||||
return FilterResult.Success;
|
||||
}
|
||||
if (controlCode != ((byte)Dlt645_2007Send.ControlCode) + 0x80)//控制码不符合时,返回错误
|
||||
{
|
||||
this.ErrorMessage =
|
||||
DltResource.Localizer["FunctionNotSame", $"0x{controlCode:X2}", $"0x{(byte)Dlt645_2007Send.ControlCode:X2}"];
|
||||
this.OperCode = 999;
|
||||
return FilterResult.Success;
|
||||
}
|
||||
if (Dlt645_2007Send.ControlCode == ControlCode.Read || Dlt645_2007Send.ControlCode == ControlCode.Write)
|
||||
{
|
||||
//数据标识不符合时,返回错误
|
||||
Response.DataId = byteBlock.Span.Slice(HeadCodeIndex + 10, 4).ToArray().BytesAdd(-0x33);
|
||||
if (!Response.DataId.SequenceEqual(Request.DataId))
|
||||
{
|
||||
this.ErrorMessage = DltResource.Localizer["DataIdNotSame"];
|
||||
this.OperCode = 999;
|
||||
return FilterResult.Success;
|
||||
}
|
||||
}
|
||||
|
||||
this.OperCode = 0;
|
||||
this.Content = byteBlock.ToArray(HeadCodeIndex + 10, BodyLength - 2);
|
||||
return FilterResult.Success;
|
||||
}
|
||||
|
||||
return FilterResult.GoOn;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,158 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://kimdiego2098.github.io/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using System.Text;
|
||||
|
||||
using ThingsGateway.Foundation.Extension.String;
|
||||
|
||||
using TouchSocket.Core;
|
||||
|
||||
namespace ThingsGateway.Foundation.Dlt645;
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc/>
|
||||
/// </summary>
|
||||
public class Dlt645_2007Request
|
||||
{
|
||||
#region Request
|
||||
|
||||
/// <summary>
|
||||
/// 数据标识
|
||||
/// </summary>
|
||||
public byte[] DataId { get; set; } = Array.Empty<byte>();
|
||||
|
||||
/// <summary>
|
||||
/// 反转解析
|
||||
/// </summary>
|
||||
public bool Reverse { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// 站号信息
|
||||
/// </summary>
|
||||
public byte[] Station { get; set; } = Array.Empty<byte>();
|
||||
|
||||
#endregion Request
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc/>
|
||||
/// </summary>
|
||||
internal class Dlt645_2007Send : ISendMessage
|
||||
{
|
||||
public int Sign { get; set; }
|
||||
|
||||
public int MaxLength => 300;
|
||||
|
||||
internal Dlt645_2007Address Dlt645_2007Address { get; }
|
||||
public int SendHeadCodeIndex { get; private set; }
|
||||
|
||||
internal ControlCode ControlCode = default;
|
||||
|
||||
private byte[] Fehead = default;
|
||||
|
||||
/// <summary>
|
||||
/// 密码、操作码
|
||||
/// </summary>
|
||||
private byte[] Codes = default;
|
||||
|
||||
/// <summary>
|
||||
/// 写入值
|
||||
/// </summary>
|
||||
private string[] Datas = default;
|
||||
|
||||
public Dlt645_2007Send(Dlt645_2007Address dlt645_2007Address, ushort sign, ControlCode controlCode, byte[] fehead = default, byte[] codes = default, string[] datas = default)
|
||||
{
|
||||
Sign = sign;
|
||||
Dlt645_2007Address = dlt645_2007Address;
|
||||
ControlCode = controlCode;
|
||||
|
||||
Fehead = fehead ?? Array.Empty<byte>();
|
||||
Codes = codes ?? Array.Empty<byte>();
|
||||
Datas = datas ?? Array.Empty<string>();
|
||||
}
|
||||
|
||||
public void Build<TByteBlock>(ref TByteBlock byteBlock) where TByteBlock : IByteBlock
|
||||
{
|
||||
if (Dlt645_2007Address?.DataId.Length < 4)
|
||||
{
|
||||
throw new(DltResource.Localizer["DataIdError"]);
|
||||
}
|
||||
if (Fehead.Length > 0)
|
||||
{
|
||||
byteBlock.Write(Fehead);//帧起始符
|
||||
SendHeadCodeIndex = Fehead.Length;
|
||||
}
|
||||
|
||||
byteBlock.WriteByte(0x68);//帧起始符
|
||||
byteBlock.Write(Dlt645_2007Address.Station);//6个字节地址域
|
||||
byteBlock.WriteByte(0x68);//帧起始符
|
||||
byteBlock.WriteByte((byte)ControlCode);//控制码
|
||||
|
||||
byteBlock.WriteByte((byte)(Dlt645_2007Address.DataId.Length));//数据域长度
|
||||
byteBlock.Write(Dlt645_2007Address.DataId);//数据域标识DI3、DI2、DI1、DI0
|
||||
|
||||
byteBlock.Write(Codes);
|
||||
|
||||
if (Datas.Length > 0)
|
||||
{
|
||||
var dataInfos = Dlt645Helper.GetDataInfos(Dlt645_2007Address.DataId);
|
||||
if (Datas.Length != dataInfos.Count)
|
||||
{
|
||||
throw new(DltResource.Localizer["CountError"]);
|
||||
}
|
||||
for (int i = 0; i < Datas.Length; i++)
|
||||
{
|
||||
var dataInfo = dataInfos[i];
|
||||
byte[] data;
|
||||
if (dataInfo.IsSigned)//可能为负数
|
||||
{
|
||||
var doubleValue = Convert.ToDouble(Datas[i]);
|
||||
if (dataInfo.Digtal != 0)//无小数点
|
||||
{
|
||||
doubleValue *= Math.Pow(10.0, dataInfo.Digtal);
|
||||
}
|
||||
data = doubleValue.ToString().HexStringToBytes().Reverse().ToArray();
|
||||
if (doubleValue < 0)
|
||||
{
|
||||
data[0] = (byte)(data[0] & 0x80);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (dataInfo.Digtal < 0)
|
||||
{
|
||||
data = Encoding.ASCII.GetBytes(Datas[i]).Reverse().ToArray();
|
||||
}
|
||||
else if (dataInfo.Digtal == 0)//无小数点
|
||||
{
|
||||
data = Datas[i].HexStringToBytes().Reverse().ToArray();
|
||||
}
|
||||
else
|
||||
{
|
||||
data = (Convert.ToDouble(Datas[i]) * Math.Pow(10.0, dataInfo.Digtal)).ToString().HexStringToBytes().Reverse().ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
byteBlock.Write(data);
|
||||
}
|
||||
}
|
||||
|
||||
byteBlock[Fehead.Length + 9] = (byte)(byteBlock.Length - 10 - Fehead.Length);//数据域长度
|
||||
|
||||
for (int index = Fehead.Length + 10; index < byteBlock.Length; ++index)
|
||||
byteBlock[index] += 0x33;//传输时发送方按字节进行加33H处理,接收方按字节进行减33H处理
|
||||
|
||||
int num = 0;
|
||||
for (int index = Fehead.Length; index < byteBlock.Length; ++index)
|
||||
num += byteBlock[index];
|
||||
byteBlock.WriteByte((byte)num);//校验码,总加和
|
||||
byteBlock.WriteByte((byte)0x16);//结束符
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://kimdiego2098.github.io/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using TouchSocket.Core;
|
||||
|
||||
namespace ThingsGateway.Foundation.Dlt645;
|
||||
|
||||
/// <summary>
|
||||
/// Dlt645_2007UdpDataHandleAdapter
|
||||
/// </summary>
|
||||
internal class Dlt645_2007UdpDataHandleAdapter : ReadWriteDevicesUdpDataHandleAdapter<Dlt645_2007Message>
|
||||
{
|
||||
protected override AdapterResult UnpackResponse(Dlt645_2007Message request, IByteBlock byteBlock)
|
||||
{
|
||||
return Dlt645Helper.GetResponse(request, byteBlock);
|
||||
}
|
||||
}
|
||||
@@ -37,12 +37,12 @@ public class Dlt645_2007Master : ProtocolBase, IDtu
|
||||
public string Password { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string Station { get; set; }
|
||||
public string Station { get; set; } = "111111111111";
|
||||
|
||||
/// <summary>
|
||||
/// 默认Dtu注册包,utf-8字符串
|
||||
/// </summary>
|
||||
public string DtuId { get; set; } = "TEST";
|
||||
public string DtuId { get; set; } = "DtuId";
|
||||
|
||||
/// <summary>
|
||||
/// 客户端连接滑动过期时间(TCP服务通道时)
|
||||
@@ -79,18 +79,18 @@ public class Dlt645_2007Master : ProtocolBase, IDtu
|
||||
case ChannelTypeEnum.TcpClient:
|
||||
case ChannelTypeEnum.TcpService:
|
||||
case ChannelTypeEnum.SerialPort:
|
||||
return new Dlt645_2007DataHandleAdapter
|
||||
return new ProtocolSingleStreamDataHandleAdapter<Dlt645_2007Message>
|
||||
{
|
||||
CacheTimeout = TimeSpan.FromMilliseconds(CacheTimeout)
|
||||
};
|
||||
|
||||
case ChannelTypeEnum.UdpSession:
|
||||
return new Dlt645_2007UdpDataHandleAdapter()
|
||||
return new ProtocolUdpDataHandleAdapter<Dlt645_2007Message>()
|
||||
{
|
||||
};
|
||||
}
|
||||
|
||||
return new Dlt645_2007DataHandleAdapter
|
||||
return new ProtocolSingleStreamDataHandleAdapter<Dlt645_2007Message>
|
||||
{
|
||||
CacheTimeout = TimeSpan.FromMilliseconds(CacheTimeout)
|
||||
};
|
||||
@@ -103,33 +103,17 @@ public class Dlt645_2007Master : ProtocolBase, IDtu
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override async ValueTask<OperResult<string[]>> ReadStringAsync(string address, int length, IThingsGatewayBitConverter bitConverter = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
bitConverter ??= ThingsGatewayBitConverter.GetTransByAddress(ref address);
|
||||
var result = await ReadAsync(address, GetLength(address, length, 8), cancellationToken).ConfigureAwait(false);
|
||||
return result.OperResultFrom(() => new[] { bitConverter.ToString(result.Content, 0, length) });
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override async ValueTask<OperResult<byte[]>> ReadAsync(string address, int length, CancellationToken cancellationToken = default)
|
||||
public async ValueTask<OperResult<byte[]>> Dlt645RequestAsync(Dlt645_2007Address dAddress, ControlCode controlCode, string feHead, byte[] codes = default, string[] datas = default, CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
var dAddress = Dlt645_2007Address.ParseFrom(address, Station, DtuId);
|
||||
var channelResult = GetChannel(dAddress.SocketId);
|
||||
if (!channelResult.IsSuccess) return new OperResult<byte[]>(channelResult);
|
||||
ValueByteBlock valueByteBlock = new ValueByteBlock(1024);
|
||||
try
|
||||
{
|
||||
var waitData = channelResult.Content.WaitHandlePool.GetWaitDataAsync(out var sign);
|
||||
var commandResult = Dlt645Helper.GetDlt645_2007Command(ref valueByteBlock, dAddress, (byte)ControlCode.Read, Station, FEHead);
|
||||
if (!commandResult.IsSuccess) return new OperResult<byte[]>(commandResult);
|
||||
return await this.SendThenReturnAsync(new SendMessage(commandResult.Content) { Sign = sign }, waitData, cancellationToken, channelResult.Content).ConfigureAwait(false);
|
||||
}
|
||||
finally
|
||||
{
|
||||
valueByteBlock.SafeDispose();
|
||||
}
|
||||
|
||||
var waitData = channelResult.Content.WaitHandlePool.GetWaitDataAsync(out var sign);
|
||||
return await this.SendThenReturnAsync(
|
||||
GetSendMessage(dAddress, (ushort)sign, controlCode, feHead, codes, datas),
|
||||
channelResult.Content, waitData, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -137,6 +121,44 @@ public class Dlt645_2007Master : ProtocolBase, IDtu
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async ValueTask<OperResult> Dlt645SendAsync(Dlt645_2007Address dAddress, ControlCode controlCode, string feHead, byte[] codes = default, string[] datas = default, CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
var channelResult = GetChannel(dAddress.SocketId);
|
||||
if (!channelResult.IsSuccess) return new OperResult<byte[]>(channelResult);
|
||||
|
||||
await this.SendAsync(
|
||||
GetSendMessage(dAddress, (ushort)0, controlCode, feHead, codes, datas),
|
||||
channelResult.Content, cancellationToken).ConfigureAwait(false);
|
||||
return OperResult.Success;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new OperResult<byte[]>(ex);
|
||||
}
|
||||
}
|
||||
|
||||
private ISendMessage GetSendMessage(Dlt645_2007Address dAddress, ushort sign, ControlCode read, string feHead, byte[] codes = default, string[] datas = default)
|
||||
{
|
||||
return new Dlt645_2007Send(dAddress, sign, read, feHead.HexStringToBytes(), codes, datas);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override ValueTask<OperResult<byte[]>> ReadAsync(string address, int length, CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
var dAddress = Dlt645_2007Address.ParseFrom(address, Station, DtuId);
|
||||
return Dlt645RequestAsync(dAddress, ControlCode.Read, FEHead, cancellationToken: cancellationToken);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return EasyValueTask.FromResult(new OperResult<byte[]>(ex));
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override async ValueTask<OperResult> WriteAsync(string address, string value, IThingsGatewayBitConverter bitConverter = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
@@ -149,24 +171,10 @@ public class Dlt645_2007Master : ProtocolBase, IDtu
|
||||
if (OperCode.Length < 8)
|
||||
OperCode = OperCode.PadLeft(8, '0');
|
||||
|
||||
var data = DataTransUtil.SpliceArray(Password.ByHexStringToBytes(), OperCode.ByHexStringToBytes());
|
||||
var codes = DataTransUtil.SpliceArray(Password.HexStringToBytes(), OperCode.HexStringToBytes());
|
||||
string[] strArray = value.SplitStringBySemicolon();
|
||||
var dAddress = Dlt645_2007Address.ParseFrom(address, Station, DtuId);
|
||||
|
||||
var channelResult = GetChannel(dAddress.SocketId);
|
||||
if (!channelResult.IsSuccess) return new OperResult<byte[]>(channelResult);
|
||||
ValueByteBlock valueByteBlock = new ValueByteBlock(1024);
|
||||
try
|
||||
{
|
||||
var waitData = channelResult.Content.WaitHandlePool.GetWaitDataAsync(out var sign);
|
||||
var commandResult = Dlt645Helper.GetDlt645_2007Command(ref valueByteBlock, dAddress, (byte)ControlCode.Write, Station, FEHead, data, strArray);
|
||||
if (!commandResult.IsSuccess) return new OperResult<byte[]>(commandResult);
|
||||
return await this.SendThenReturnAsync(new SendMessage(commandResult.Content) { Sign = sign }, waitData, cancellationToken, channelResult.Content).ConfigureAwait(false);
|
||||
}
|
||||
finally
|
||||
{
|
||||
valueByteBlock.SafeDispose();
|
||||
}
|
||||
return await Dlt645RequestAsync(dAddress, ControlCode.Write, FEHead, codes, strArray, cancellationToken: cancellationToken);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -177,7 +185,26 @@ public class Dlt645_2007Master : ProtocolBase, IDtu
|
||||
#region
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override ValueTask<OperResult> WriteAsync(string address, byte[] value, CancellationToken cancellationToken = default) => throw new NotSupportedException();
|
||||
public override async ValueTask<OperResult> WriteAsync(string address, byte[] value, CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
Password ??= string.Empty;
|
||||
OperCode ??= string.Empty;
|
||||
if (Password.Length < 8)
|
||||
Password = Password.PadLeft(8, '0');
|
||||
if (OperCode.Length < 8)
|
||||
OperCode = OperCode.PadLeft(8, '0');
|
||||
|
||||
var codes = DataTransUtil.SpliceArray(Password.HexStringToBytes(), OperCode.HexStringToBytes());
|
||||
var dAddress = Dlt645_2007Address.ParseFrom(address, Station, DtuId);
|
||||
return await Dlt645RequestAsync(dAddress, ControlCode.Write, FEHead, codes, cancellationToken: cancellationToken);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new OperResult<byte[]>(ex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override ValueTask<OperResult> WriteAsync(string address, uint value, IThingsGatewayBitConverter bitConverter = null, CancellationToken cancellationToken = default) => WriteAsync(address, value.ToString(), bitConverter, cancellationToken);
|
||||
@@ -221,18 +248,13 @@ public class Dlt645_2007Master : ProtocolBase, IDtu
|
||||
{
|
||||
try
|
||||
{
|
||||
ValueByteBlock valueByteBlock = new ValueByteBlock(1024);
|
||||
try
|
||||
{
|
||||
string str = $"{dateTime.Second:D2}{dateTime.Minute:D2}{dateTime.Hour:D2}{dateTime.Day:D2}{dateTime.Month:D2}{dateTime.Year % 100:D2}";
|
||||
var commandResult = Dlt645Helper.GetDlt645_2007Command(ref valueByteBlock, (byte)ControlCode.BroadcastTime, str.ByHexStringToBytes().ToArray(), "999999999999".ByHexStringToBytes(), FEHead);
|
||||
await this.SendAsync(socketId, new SendMessage(commandResult), cancellationToken).ConfigureAwait(false);
|
||||
return OperResult.Success;
|
||||
}
|
||||
finally
|
||||
{
|
||||
valueByteBlock.SafeDispose();
|
||||
}
|
||||
string str = $"{dateTime.Second:D2}{dateTime.Minute:D2}{dateTime.Hour:D2}{dateTime.Day:D2}{dateTime.Month:D2}{dateTime.Year % 100:D2}";
|
||||
Dlt645_2007Address dAddress = new();
|
||||
dAddress.Station = str.HexStringToBytes();
|
||||
dAddress.DataId = "999999999999".HexStringToBytes();
|
||||
dAddress.SocketId = socketId;
|
||||
|
||||
return await Dlt645SendAsync(dAddress, ControlCode.BroadcastTime, FEHead, cancellationToken: cancellationToken);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -245,26 +267,20 @@ public class Dlt645_2007Master : ProtocolBase, IDtu
|
||||
/// </summary>
|
||||
/// <param name="socketId">socketId</param>
|
||||
/// <param name="dateTime">时间</param>
|
||||
/// <param name="station">表号</param>
|
||||
/// <param name="cancellationToken">取消令箭</param>
|
||||
/// <returns></returns>
|
||||
public async ValueTask<OperResult> FreezeAsync(string socketId, DateTime dateTime, CancellationToken cancellationToken = default)
|
||||
public async ValueTask<OperResult> FreezeAsync(string socketId, DateTime dateTime, string station = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
string str = $"{dateTime.Minute:D2}{dateTime.Hour:D2}{dateTime.Day:D2}{dateTime.Month:D2}";
|
||||
if (Station.IsNullOrEmpty()) Station = string.Empty;
|
||||
if (Station.Length < 12) Station = Station.PadLeft(12, '0');
|
||||
Dlt645_2007Address dAddress = new();
|
||||
dAddress.SetStation(station ?? Station);
|
||||
dAddress.DataId = str.HexStringToBytes();
|
||||
dAddress.SocketId = socketId;
|
||||
|
||||
ValueByteBlock valueByteBlock = new ValueByteBlock(1024);
|
||||
try
|
||||
{
|
||||
var commandResult = Dlt645Helper.GetDlt645_2007Command(ref valueByteBlock, (byte)ControlCode.Freeze, str.ByHexStringToBytes().ToArray(), Station.ByHexStringToBytes().Reverse().ToArray(), FEHead);
|
||||
return await this.SendThenReturnAsync(socketId, commandResult, cancellationToken: cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
finally
|
||||
{
|
||||
valueByteBlock.SafeDispose();
|
||||
}
|
||||
return await Dlt645RequestAsync(dAddress, ControlCode.Freeze, FEHead, cancellationToken: cancellationToken);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -282,24 +298,19 @@ public class Dlt645_2007Master : ProtocolBase, IDtu
|
||||
{
|
||||
try
|
||||
{
|
||||
ValueByteBlock valueByteBlock = new ValueByteBlock(1024);
|
||||
try
|
||||
Dlt645_2007Address dAddress = new();
|
||||
dAddress.SetStation("AAAAAAAAAAAA");
|
||||
dAddress.SocketId = socketId;
|
||||
|
||||
var result = await Dlt645RequestAsync(dAddress, ControlCode.ReadStation, FEHead, cancellationToken: cancellationToken);
|
||||
if (result.IsSuccess)
|
||||
{
|
||||
var commandResult = Dlt645Helper.GetDlt645_2007Command(ref valueByteBlock, (byte)ControlCode.ReadStation, null, "AAAAAAAAAAAA".ByHexStringToBytes(), FEHead);
|
||||
var result = await this.SendThenReturnAsync(socketId, commandResult, cancellationToken: cancellationToken).ConfigureAwait(false);
|
||||
if (result.IsSuccess)
|
||||
{
|
||||
var buffer = result.Content.SelectMiddle(0, 6).BytesAdd(-0x33);
|
||||
return OperResult.CreateSuccessResult(buffer.Reverse().ToArray().ToHexString());
|
||||
}
|
||||
else
|
||||
{
|
||||
return new OperResult<string>(result);
|
||||
}
|
||||
var buffer = result.Content.SelectMiddle(0, 6).BytesAdd(-0x33);
|
||||
return OperResult.CreateSuccessResult(buffer.Reverse().ToArray().ToHexString());
|
||||
}
|
||||
finally
|
||||
else
|
||||
{
|
||||
valueByteBlock.SafeDispose();
|
||||
return new OperResult<string>(result);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -313,9 +324,10 @@ public class Dlt645_2007Master : ProtocolBase, IDtu
|
||||
/// </summary>
|
||||
/// <param name="socketId">socketId</param>
|
||||
/// <param name="baudRate">波特率</param>
|
||||
/// <param name="station">表号</param>
|
||||
/// <param name="cancellationToken">取消令箭</param>
|
||||
/// <returns></returns>
|
||||
public async ValueTask<OperResult> WriteBaudRateAsync(string socketId, int baudRate, CancellationToken cancellationToken = default)
|
||||
public async ValueTask<OperResult> WriteBaudRateAsync(string socketId, int baudRate, string station = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -330,19 +342,13 @@ public class Dlt645_2007Master : ProtocolBase, IDtu
|
||||
case 19200: baudRateByte = 0x40; break;
|
||||
default: return new OperResult<string>(DltResource.Localizer["BaudRateError", baudRate]);
|
||||
}
|
||||
if (Station.IsNullOrEmpty()) Station = string.Empty;
|
||||
if (Station.Length < 12) Station = Station.PadLeft(12, '0');
|
||||
|
||||
ValueByteBlock valueByteBlock = new ValueByteBlock(1024);
|
||||
try
|
||||
{
|
||||
var commandResult = Dlt645Helper.GetDlt645_2007Command(ref valueByteBlock, (byte)ControlCode.WriteBaudRate, new byte[1] { baudRateByte }, Station.ByHexStringToBytes().Reverse().ToArray(), FEHead);
|
||||
return await this.SendThenReturnAsync(socketId, commandResult, cancellationToken: cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
finally
|
||||
{
|
||||
valueByteBlock.SafeDispose();
|
||||
}
|
||||
Dlt645_2007Address dAddress = new();
|
||||
dAddress.SetStation(station ?? Station);
|
||||
dAddress.SocketId = socketId;
|
||||
dAddress.DataId = [baudRateByte];
|
||||
|
||||
return await Dlt645RequestAsync(dAddress, ControlCode.ReadStation, FEHead, cancellationToken: cancellationToken);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -361,16 +367,12 @@ public class Dlt645_2007Master : ProtocolBase, IDtu
|
||||
{
|
||||
try
|
||||
{
|
||||
ValueByteBlock valueByteBlock = new ValueByteBlock(1024);
|
||||
try
|
||||
{
|
||||
var commandResult = Dlt645Helper.GetDlt645_2007Command(ref valueByteBlock, (byte)ControlCode.WriteStation, station.ByHexStringToBytes().Reverse().ToArray(), "AAAAAAAAAAAA".ByHexStringToBytes(), FEHead);
|
||||
return await this.SendThenReturnAsync(socketId, commandResult, cancellationToken: cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
finally
|
||||
{
|
||||
valueByteBlock.SafeDispose();
|
||||
}
|
||||
Dlt645_2007Address dAddress = new();
|
||||
dAddress.SocketId = socketId;
|
||||
dAddress.Station = "AAAAAAAAAAAA".HexStringToBytes();
|
||||
dAddress.DataId = station.HexStringToBytes().Reverse().ToArray();
|
||||
|
||||
return await Dlt645RequestAsync(dAddress, ControlCode.WriteStation, FEHead, cancellationToken: cancellationToken);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -385,32 +387,26 @@ public class Dlt645_2007Master : ProtocolBase, IDtu
|
||||
/// <param name="level">密码等级,0-8</param>
|
||||
/// <param name="oldPassword">旧密码</param>
|
||||
/// <param name="newPassword">新密码</param>
|
||||
/// <param name="station">station</param>
|
||||
/// <param name="cancellationToken">取消令箭</param>
|
||||
/// <returns></returns>
|
||||
public async ValueTask<OperResult> WritePasswordAsync(string socketId, byte level, string oldPassword, string newPassword, CancellationToken cancellationToken = default)
|
||||
public async ValueTask<OperResult> WritePasswordAsync(string socketId, byte level, string oldPassword, string newPassword, string station = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (Station.IsNullOrEmpty()) Station = string.Empty;
|
||||
if (Station.Length < 12) Station = Station.PadLeft(12, '0');
|
||||
string str = $"04000C{level + 1:D2}";
|
||||
|
||||
var bytes = DataTransUtil.SpliceArray(str.ByHexStringToBytes().Reverse().ToArray()
|
||||
, (oldPassword.ByHexStringToBytes().Reverse().ToArray())
|
||||
, (newPassword.ByHexStringToBytes().Reverse().ToArray()));
|
||||
var bytes = DataTransUtil.SpliceArray(str.HexStringToBytes().Reverse().ToArray()
|
||||
, (oldPassword.HexStringToBytes().Reverse().ToArray())
|
||||
, (newPassword.HexStringToBytes().Reverse().ToArray()));
|
||||
|
||||
ValueByteBlock valueByteBlock = new ValueByteBlock(1024);
|
||||
try
|
||||
{
|
||||
var commandResult = Dlt645Helper.GetDlt645_2007Command(ref valueByteBlock, (byte)ControlCode.WritePassword,
|
||||
bytes
|
||||
, Station.ByHexStringToBytes().Reverse().ToArray(), FEHead);
|
||||
return await this.SendThenReturnAsync(socketId, commandResult, cancellationToken: cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
finally
|
||||
{
|
||||
valueByteBlock.SafeDispose();
|
||||
}
|
||||
Dlt645_2007Address dAddress = new();
|
||||
dAddress.SetStation(station ?? Station);
|
||||
dAddress.SocketId = socketId;
|
||||
dAddress.Station = "AAAAAAAAAAAA".HexStringToBytes();
|
||||
dAddress.DataId = bytes;
|
||||
|
||||
return await Dlt645RequestAsync(dAddress, ControlCode.WritePassword, FEHead, cancellationToken: cancellationToken);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
@@ -13,7 +13,7 @@ namespace ThingsGateway.Foundation.Dlt645;
|
||||
/// <summary>
|
||||
/// 控制码
|
||||
/// </summary>
|
||||
internal enum ControlCode : byte
|
||||
public enum ControlCode : byte
|
||||
{
|
||||
/// <summary>
|
||||
/// 读数据
|
||||
File diff suppressed because it is too large
Load Diff
@@ -21,7 +21,7 @@ namespace ThingsGateway.Foundation.Dlt645;
|
||||
/// <summary>
|
||||
/// Dlt645_2007Address
|
||||
/// </summary>
|
||||
public class Dlt645_2007Address
|
||||
public class Dlt645_2007Address : Dlt645_2007Request
|
||||
{
|
||||
/// <summary>
|
||||
/// <inheritdoc/>
|
||||
@@ -30,20 +30,25 @@ public class Dlt645_2007Address
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 数据标识
|
||||
/// </summary>
|
||||
public byte[] DataId { get; set; } = Array.Empty<byte>();
|
||||
public Dlt645_2007Address(Dlt645_2007Address dlt645_2007Address)
|
||||
{
|
||||
this.SocketId = dlt645_2007Address.SocketId;
|
||||
this.DataId = dlt645_2007Address.DataId;
|
||||
this.Reverse = dlt645_2007Address.Reverse;
|
||||
this.Station = dlt645_2007Address.Station;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 反转解析
|
||||
/// </summary>
|
||||
public bool Reverse { get; set; } = true;
|
||||
public void SetStation(string station)
|
||||
{
|
||||
if (station.Length < 12)
|
||||
station = station.PadLeft(12, '0');
|
||||
Station = station.HexStringToBytes().Reverse().ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 站号信息
|
||||
/// </summary>
|
||||
public byte[] Station { get; set; } = Array.Empty<byte>();
|
||||
public void SetDataId(string dataId)
|
||||
{
|
||||
DataId = dataId.HexStringToBytes().Reverse().ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 作为Slave时需提供的SocketId,用于分辨Socket客户端,通常对比的是初始链接时的注册包
|
||||
@@ -78,26 +83,21 @@ public class Dlt645_2007Address
|
||||
/// </summary>
|
||||
public static Dlt645_2007Address ParseFrom(string address, string defaultStation = null, string dtuid = null, bool isCache = true)
|
||||
{
|
||||
var cacheKey = $"{nameof(ParseFrom)}_{typeof(Dlt645_2007Address).FullName}_{typeof(Dlt645_2007Address).TypeHandle.Value}_{address}";
|
||||
var cacheKey = $"{nameof(ParseFrom)}_{typeof(Dlt645_2007Address).FullName}_{typeof(Dlt645_2007Address).TypeHandle.Value}_{address}_{defaultStation}_{dtuid}";
|
||||
if (isCache)
|
||||
if (MemoryCache.Instance.TryGetValue(cacheKey, out Dlt645_2007Address dAddress))
|
||||
return dAddress;
|
||||
return new(dAddress);
|
||||
|
||||
Dlt645_2007Address dlt645_2007Address = new();
|
||||
if (defaultStation != null)
|
||||
if (!defaultStation.IsNullOrEmpty())
|
||||
{
|
||||
if (defaultStation.IsNullOrEmpty()) defaultStation = string.Empty;
|
||||
if (defaultStation.Length < 12)
|
||||
defaultStation = defaultStation.PadLeft(12, '0');
|
||||
dlt645_2007Address.Station = defaultStation.ByHexStringToBytes().Reverse().ToArray();
|
||||
dlt645_2007Address.SetStation(defaultStation);
|
||||
}
|
||||
if (dtuid != null)
|
||||
dlt645_2007Address.SocketId = dtuid;
|
||||
byte[] array;
|
||||
array = Array.Empty<byte>();
|
||||
if (address.IndexOf(';') < 0)
|
||||
{
|
||||
array = address.ByHexStringToBytes().Reverse().ToArray();
|
||||
dlt645_2007Address.SetDataId(address);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -108,10 +108,9 @@ public class Dlt645_2007Address
|
||||
if (strArray[index].ToUpper().StartsWith("S="))
|
||||
{
|
||||
var station = strArray[index].Substring(2);
|
||||
if (station.IsNullOrEmpty()) station = string.Empty;
|
||||
if (station.Length < 12)
|
||||
station = station.PadLeft(12, '0');
|
||||
dlt645_2007Address.Station = station.ByHexStringToBytes().Reverse().ToArray();
|
||||
dlt645_2007Address.Station = station.HexStringToBytes().Reverse().ToArray();
|
||||
}
|
||||
else if (strArray[index].Contains("R="))
|
||||
{
|
||||
@@ -123,15 +122,14 @@ public class Dlt645_2007Address
|
||||
}
|
||||
else if (!strArray[index].Contains("="))
|
||||
{
|
||||
array = strArray[index].ByHexStringToBytes().Reverse().ToArray();
|
||||
dlt645_2007Address.SetDataId(strArray[index]);
|
||||
}
|
||||
}
|
||||
}
|
||||
dlt645_2007Address.DataId = array;
|
||||
|
||||
if (isCache)
|
||||
MemoryCache.Instance.Set(cacheKey, dlt645_2007Address, 3600);
|
||||
|
||||
return dlt645_2007Address;
|
||||
return new(dlt645_2007Address);
|
||||
}
|
||||
}
|
||||
@@ -10,8 +10,6 @@
|
||||
|
||||
using System.Text;
|
||||
|
||||
using ThingsGateway.Foundation.Extension.Generic;
|
||||
|
||||
using TouchSocket.Core;
|
||||
|
||||
namespace ThingsGateway.Foundation.Dlt645;
|
||||
@@ -94,14 +92,13 @@ public class Dlt645_2007BitConverter : ThingsGatewayBitConverter
|
||||
/// <inheritdoc/>
|
||||
public override string ToString(byte[] buffer, int offset, int length)
|
||||
{
|
||||
buffer = buffer.RemoveBegin(offset);
|
||||
buffer = buffer.BytesAdd(-0x33);
|
||||
var dataInfos = Dlt645Helper.GetDataInfos(buffer);
|
||||
var data = new ReadOnlySpan<byte>(buffer, offset, length).BytesAdd(-0x33);
|
||||
var dataInfos = Dlt645Helper.GetDataInfos(data);
|
||||
StringBuilder stringBuilder = new();
|
||||
foreach (var dataInfo in dataInfos)
|
||||
{
|
||||
//实际数据
|
||||
var content = buffer.SelectMiddle(4, dataInfo.ByteLength).Reverse().ToArray();
|
||||
var content = data.Slice(4, dataInfo.ByteLength).ToArray().Reverse().ToArray();
|
||||
if (dataInfo.IsSigned)//可能为负数
|
||||
{
|
||||
if (content[0] > 0x80)//最高位是表示正负
|
||||
@@ -129,7 +126,7 @@ public class Dlt645_2007BitConverter : ThingsGatewayBitConverter
|
||||
|
||||
return stringBuilder.ToString();
|
||||
|
||||
static void ToString(StringBuilder stringBuilder, DataInfo dataInfo, byte[] content)
|
||||
static void ToString(StringBuilder stringBuilder, Dlt645DataInfo dataInfo, byte[] content)
|
||||
{
|
||||
if (dataInfo.Digtal < 0)
|
||||
{
|
||||
@@ -33,7 +33,6 @@ internal static class PackHelper
|
||||
IThingsGatewayBitConverter transformParameter = byteConverter.GetTransByAddress(ref address);
|
||||
item.ThingsGatewayBitConverter = transformParameter;
|
||||
item.Index = 0;
|
||||
//item.Address = address;
|
||||
if (item.DataType == DataTypeEnum.Boolean)
|
||||
item.Index = device.GetBitOffset(item.RegisterAddress);
|
||||
var r = new T()
|
||||
@@ -17,94 +17,78 @@ using ThingsGateway.Foundation.Extension.String;
|
||||
namespace ThingsGateway.Foundation.Modbus;
|
||||
|
||||
/// <summary>
|
||||
/// Modbus协议地址
|
||||
/// Modbus协议地址,字符串不包含长度与写入值、读写类型
|
||||
/// </summary>
|
||||
public class ModbusAddress
|
||||
public class ModbusAddress : ModbusRequest
|
||||
{
|
||||
public ModbusAddress()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 可能带小数点的地址表示
|
||||
/// </summary>
|
||||
public string Address { get; set; }
|
||||
public ModbusAddress(ModbusRequest modbusAddress)
|
||||
{
|
||||
this.StartAddress = modbusAddress.StartAddress;
|
||||
this.Data = modbusAddress.Data;
|
||||
this.FunctionCode = modbusAddress.FunctionCode;
|
||||
this.Length = modbusAddress.Length;
|
||||
this.Station = modbusAddress.Station;
|
||||
this.StartAddress = modbusAddress.StartAddress;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 起始地址
|
||||
/// </summary>
|
||||
public ushort AddressStart => (ushort)Address.ToInt();
|
||||
|
||||
/// <summary>
|
||||
/// 读取功能码
|
||||
/// </summary>
|
||||
public byte ReadFunction { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 站号信息
|
||||
/// </summary>
|
||||
public byte Station { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 写入功能码
|
||||
/// </summary>
|
||||
public byte? WriteFunction { get; set; }
|
||||
public ModbusAddress(ModbusAddress modbusAddress)
|
||||
{
|
||||
this.StartAddress = modbusAddress.StartAddress;
|
||||
this.Data = modbusAddress.Data;
|
||||
this.FunctionCode = modbusAddress.FunctionCode;
|
||||
this.Length = modbusAddress.Length;
|
||||
this.BitIndex = modbusAddress.BitIndex;
|
||||
this.SocketId = modbusAddress.SocketId;
|
||||
this.WriteFunctionCode = modbusAddress.WriteFunctionCode;
|
||||
this.Station = modbusAddress.Station;
|
||||
this.StartAddress = modbusAddress.StartAddress;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// BitIndex
|
||||
/// </summary>
|
||||
public int? BitIndex
|
||||
{
|
||||
get
|
||||
{
|
||||
var data = Address?.SplitStringByDelimiter();
|
||||
if (data?.Length == 2)
|
||||
{
|
||||
return Convert.ToInt32(data[1]);
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
public int? BitIndex { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 打包临时写入,需要读取的字节长度
|
||||
/// 写入功能码,主站请求时生效,其余情况应选择<see cref="ModbusRequest.FunctionCode"/>
|
||||
/// </summary>
|
||||
public int? ByteLength { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 读取终止
|
||||
/// </summary>
|
||||
public uint AddressEnd => (ushort)(AddressStart + (ByteLength != null ? (Math.Ceiling(ByteLength!.Value / 2.0) > 0 ? Math.Ceiling(ByteLength!.Value / 2.0) : 1) : 1));
|
||||
public byte? WriteFunctionCode { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 作为Slave时需提供的SocketId,用于分辨Socket客户端,通常对比的是初始链接时的注册包
|
||||
/// </summary>
|
||||
public string? SocketId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 读取终止
|
||||
/// </summary>
|
||||
public int AddressEnd => (ushort)(StartAddress + Math.Max(Math.Ceiling(Length / 2.0), 1));
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string ToString()
|
||||
{
|
||||
StringBuilder stringGeter = new();
|
||||
if (Station > 0)
|
||||
{
|
||||
stringGeter.Append($"s={Station};");
|
||||
stringGeter.Append($"S={Station};");
|
||||
}
|
||||
if (WriteFunction > 0)
|
||||
if (WriteFunctionCode > 0)
|
||||
{
|
||||
stringGeter.Append($"w={WriteFunction};");
|
||||
stringGeter.Append($"W={WriteFunctionCode};");
|
||||
}
|
||||
if (!string.IsNullOrEmpty(SocketId))
|
||||
{
|
||||
stringGeter.Append($"id={SocketId};");
|
||||
stringGeter.Append($"ID={SocketId};");
|
||||
}
|
||||
stringGeter.Append($"{GetFunctionString(ReadFunction)}{AddressStart + 1}{(BitIndex != null ? $".{BitIndex}" : null)}");
|
||||
stringGeter.Append($"{GetFunctionString(FunctionCode)}{StartAddress + 1}{(BitIndex != null ? $".{BitIndex}" : null)}");
|
||||
return stringGeter.ToString();
|
||||
}
|
||||
|
||||
private string GetFunctionString(int readF)
|
||||
private string GetFunctionString(byte readF)
|
||||
{
|
||||
return readF switch
|
||||
{
|
||||
@@ -125,7 +109,7 @@ public class ModbusAddress
|
||||
var cacheKey = $"{nameof(ParseFrom)}_{typeof(ModbusAddress).FullName}_{typeof(ModbusAddress).TypeHandle.Value}_{station}_{dtuid}_{address}";
|
||||
if (isCache)
|
||||
if (MemoryCache.Instance.TryGetValue(cacheKey, out ModbusAddress mAddress))
|
||||
return mAddress;
|
||||
return new(mAddress);
|
||||
|
||||
var modbusAddress = new ModbusAddress();
|
||||
if (station != null)
|
||||
@@ -143,13 +127,13 @@ public class ModbusAddress
|
||||
{
|
||||
if (strArray[index].ToUpper().StartsWith("S="))
|
||||
{
|
||||
if (Convert.ToInt16(strArray[index].Substring(2)) > 0)
|
||||
if (Convert.ToByte(strArray[index].Substring(2)) > 0)
|
||||
modbusAddress.Station = byte.Parse(strArray[index].Substring(2));
|
||||
}
|
||||
else if (strArray[index].ToUpper().StartsWith("W="))
|
||||
{
|
||||
if (Convert.ToInt16(strArray[index].Substring(2)) > 0)
|
||||
modbusAddress.WriteFunction = byte.Parse(strArray[index].Substring(2));
|
||||
if (Convert.ToByte(strArray[index].Substring(2)) > 0)
|
||||
modbusAddress.WriteFunctionCode = byte.Parse(strArray[index].Substring(2));
|
||||
}
|
||||
else if (strArray[index].ToUpper().StartsWith("ID="))
|
||||
{
|
||||
@@ -165,7 +149,7 @@ public class ModbusAddress
|
||||
if (isCache)
|
||||
MemoryCache.Instance.Set(cacheKey, modbusAddress, 3600);
|
||||
|
||||
return modbusAddress;
|
||||
return new(modbusAddress);
|
||||
|
||||
void Address(string address)
|
||||
{
|
||||
@@ -175,29 +159,26 @@ public class ModbusAddress
|
||||
switch (readF)
|
||||
{
|
||||
case 0:
|
||||
modbusAddress.ReadFunction = 1;
|
||||
modbusAddress.FunctionCode = 1;
|
||||
break;
|
||||
|
||||
case 1:
|
||||
modbusAddress.ReadFunction = 2;
|
||||
modbusAddress.FunctionCode = 2;
|
||||
break;
|
||||
|
||||
case 3:
|
||||
modbusAddress.ReadFunction = 4;
|
||||
modbusAddress.FunctionCode = 4;
|
||||
break;
|
||||
|
||||
case 4:
|
||||
modbusAddress.ReadFunction = 3;
|
||||
modbusAddress.FunctionCode = 3;
|
||||
break;
|
||||
}
|
||||
string[] strArray = address.SplitStringByDelimiter();
|
||||
modbusAddress.StartAddress = (ushort)(ushort.Parse(strArray[0].Substring(1)) - 1);
|
||||
if (strArray.Length > 1)
|
||||
{
|
||||
modbusAddress.Address = (ushort.Parse(strArray[0].Substring(1)) - 1).ToString() + '.' + strArray[1];
|
||||
}
|
||||
else
|
||||
{
|
||||
modbusAddress.Address = (ushort.Parse(strArray[0].Substring(1)) - 1).ToString();
|
||||
modbusAddress.BitIndex = int.Parse(strArray[1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,58 +12,6 @@ namespace ThingsGateway.Foundation.Modbus;
|
||||
|
||||
internal class ModbusHelper
|
||||
{
|
||||
#region modbusServer
|
||||
|
||||
internal static byte[] ModbusServerAnalysisAddressValue(IModbusServerMessage request, IByteBlock response)
|
||||
{
|
||||
var offset = response.Position;
|
||||
//解析01 03 00 00 00 0A
|
||||
var station = response[0 + offset];
|
||||
var function = response[1 + offset];
|
||||
response.Position = 2 + offset;
|
||||
int addressStart = response.ReadUInt16(EndianType.Big);
|
||||
if (function > 4)
|
||||
{
|
||||
if (function > 6)
|
||||
{
|
||||
request.ModbusAddress = new ModbusAddress()
|
||||
{
|
||||
Station = station,
|
||||
Address = addressStart.ToString(),
|
||||
WriteFunction = function,
|
||||
ReadFunction = (byte)(function == 16 ? 3 : function == 15 ? 1 : 3),
|
||||
};
|
||||
request.Length = response[5 + offset];
|
||||
return response.ToArray(7 + offset);
|
||||
}
|
||||
else
|
||||
{
|
||||
request.ModbusAddress = new ModbusAddress()
|
||||
{
|
||||
Station = station,
|
||||
Address = addressStart.ToString(),
|
||||
WriteFunction = function,
|
||||
ReadFunction = (byte)(function == 6 ? 3 : function == 5 ? 1 : 3),
|
||||
};
|
||||
request.Length = 1;
|
||||
return response.ToArray(4 + offset);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
request.ModbusAddress = new ModbusAddress()
|
||||
{
|
||||
Station = station,
|
||||
Address = addressStart.ToString(),
|
||||
ReadFunction = function,
|
||||
};
|
||||
request.Length = response[5 + offset];
|
||||
return Array.Empty<byte>();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion modbusServer
|
||||
|
||||
#region 解析
|
||||
|
||||
/// <summary>
|
||||
@@ -95,225 +43,5 @@ internal class ModbusHelper
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取modbus数据区内容,response需去除Crc和tcp报文头,例如:01 03 02 00 01
|
||||
/// </summary>
|
||||
/// <param name="send">发送数据</param>
|
||||
/// <param name="response">返回数据</param>
|
||||
/// <returns></returns>
|
||||
internal static OperResult<AdapterResult> GetModbusData(ReadOnlySpan<byte> send, IByteBlock response)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (response[response.Position + 1] >= 0x80)//错误码
|
||||
return new OperResult<AdapterResult>(GetDescriptionByErrorCode(response[response.Position + 2])) { Content = new AdapterResult() { FilterResult = FilterResult.Success } };
|
||||
|
||||
if (send.Length == 0)
|
||||
{
|
||||
return new OperResult<AdapterResult>()
|
||||
{
|
||||
Content = new()
|
||||
{
|
||||
Content = response.ToArray(response.Position + 3, response[response.Position + 2]),
|
||||
FilterResult = FilterResult.Success
|
||||
}
|
||||
};
|
||||
}
|
||||
//站号验证
|
||||
if (send[response.Position + 0] != response[response.Position + 0])
|
||||
return new OperResult<AdapterResult>(ModbusResource.Localizer["StationNotSame", send[response.Position + 0], response[response.Position + 0]])
|
||||
{
|
||||
Content = new()
|
||||
{
|
||||
FilterResult = FilterResult.Success
|
||||
}
|
||||
};
|
||||
//功能码验证
|
||||
if (send[response.Position + 1] != response[response.Position + 1])
|
||||
return new OperResult<AdapterResult>(ModbusResource.Localizer["FunctionNotSame", send[response.Position + 1], response[response.Position + 1]])
|
||||
{
|
||||
Content = new()
|
||||
{
|
||||
FilterResult = FilterResult.Success
|
||||
}
|
||||
};
|
||||
return new OperResult<AdapterResult>()
|
||||
{
|
||||
Content = new()
|
||||
{
|
||||
Content = response.ToArray(response.Position + 3, response[response.Position + 2]),
|
||||
FilterResult = FilterResult.Success
|
||||
}
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new OperResult<AdapterResult>(ex)
|
||||
{
|
||||
Content = new()
|
||||
{
|
||||
FilterResult = FilterResult.Success
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检测crc
|
||||
/// </summary>
|
||||
/// <param name="response"></param>
|
||||
/// <returns></returns>
|
||||
internal static OperResult CheckCrc(IByteBlock response)
|
||||
{
|
||||
//crc校验
|
||||
var dataLen = response.Length;
|
||||
var crc = CRC16Utils.Crc16Only(new ReadOnlyMemory<byte>(response.AsSegment().Array, 0, dataLen - 2));
|
||||
if ((response[dataLen - 2] != crc[0] || response[dataLen - 1] != crc[1]))
|
||||
return new OperResult($"{ModbusResource.Localizer["CrcError"]} {DataTransUtil.ByteToHexString(response.Span, ' ')}")
|
||||
{
|
||||
};
|
||||
response.SetLength(dataLen - 2);
|
||||
return OperResult.Success;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 去除Crc,返回modbus数据区
|
||||
/// </summary>
|
||||
/// <param name="send"></param>
|
||||
/// <param name="response"></param>
|
||||
/// <returns></returns>
|
||||
internal static OperResult<AdapterResult> GetModbusRtuData(ReadOnlySpan<byte> send, IByteBlock response)
|
||||
{
|
||||
var result = CheckCrc(response);
|
||||
if (!result.IsSuccess)
|
||||
{
|
||||
return new OperResult<AdapterResult>(result)
|
||||
{
|
||||
Content = new() { FilterResult = FilterResult.Success }
|
||||
};
|
||||
}
|
||||
|
||||
return GetModbusData(send, response);
|
||||
}
|
||||
|
||||
#endregion 解析
|
||||
|
||||
#region 报文构建
|
||||
|
||||
/// <summary>
|
||||
/// 获取读取报文
|
||||
/// </summary>
|
||||
internal static void GetReadModbusCommand(ref ValueByteBlock valueByteBlock, ModbusAddress mAddress, ushort length, ModbusTypeEnum modbusType, ushort sign, ushort protocolId)
|
||||
{
|
||||
if (modbusType == ModbusTypeEnum.ModbusTcp)
|
||||
{
|
||||
valueByteBlock.WriteUInt16(sign, EndianType.Big);
|
||||
valueByteBlock.WriteUInt16(protocolId, EndianType.Big);
|
||||
valueByteBlock.WriteUInt16(6, EndianType.Big);
|
||||
}
|
||||
valueByteBlock.WriteByte(mAddress.Station);
|
||||
valueByteBlock.WriteByte(mAddress.ReadFunction);
|
||||
valueByteBlock.WriteUInt16(mAddress.AddressStart, EndianType.Big);
|
||||
valueByteBlock.WriteUInt16(length, EndianType.Big);
|
||||
|
||||
if (modbusType == ModbusTypeEnum.ModbusRtu)
|
||||
{
|
||||
valueByteBlock.Write(CRC16Utils.Crc16Only(valueByteBlock.Memory));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取05写入布尔量报文
|
||||
/// </summary>
|
||||
internal static void GetWriteBoolModbusCommand(ref ValueByteBlock valueByteBlock, ModbusAddress mAddress, bool value, ModbusTypeEnum modbusType, ushort sign, ushort protocolId)
|
||||
{
|
||||
if (modbusType == ModbusTypeEnum.ModbusTcp)
|
||||
{
|
||||
valueByteBlock.WriteUInt16(sign, EndianType.Big);
|
||||
valueByteBlock.WriteUInt16(protocolId, EndianType.Big);
|
||||
valueByteBlock.WriteUInt16(6, EndianType.Big);
|
||||
}
|
||||
valueByteBlock.WriteByte(mAddress.Station);
|
||||
valueByteBlock.WriteByte(5);
|
||||
valueByteBlock.WriteUInt16(mAddress.AddressStart, EndianType.Big);
|
||||
valueByteBlock.Write(value ? new byte[] { 0xFF, 0 } : new byte[] { 0, 0 });
|
||||
|
||||
if (modbusType == ModbusTypeEnum.ModbusRtu)
|
||||
{
|
||||
valueByteBlock.Write(CRC16Utils.Crc16Only(valueByteBlock.Memory));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取15写入布尔量报文
|
||||
/// </summary>
|
||||
internal static void GetWriteBoolModbusCommand(ref ValueByteBlock valueByteBlock, ModbusAddress mAddress, bool[] values, ushort length, ModbusTypeEnum modbusType, ushort sign, ushort protocolId)
|
||||
{
|
||||
if (modbusType == ModbusTypeEnum.ModbusTcp)
|
||||
{
|
||||
valueByteBlock.WriteUInt16(sign, EndianType.Big);
|
||||
valueByteBlock.WriteUInt16(protocolId, EndianType.Big);
|
||||
valueByteBlock.WriteUInt16(6, EndianType.Big);
|
||||
}
|
||||
valueByteBlock.WriteByte(mAddress.Station);
|
||||
valueByteBlock.WriteByte(15);
|
||||
valueByteBlock.WriteUInt16(mAddress.AddressStart, EndianType.Big);
|
||||
valueByteBlock.WriteUInt16(length, EndianType.Big);
|
||||
byte[] data = values.BoolArrayToByte();
|
||||
valueByteBlock.WriteByte((byte)data.Length);
|
||||
valueByteBlock.Write(values.BoolArrayToByte());
|
||||
|
||||
if (modbusType == ModbusTypeEnum.ModbusRtu)
|
||||
{
|
||||
valueByteBlock.Write(CRC16Utils.Crc16Only(valueByteBlock.Memory));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取16写入字报文
|
||||
/// </summary>
|
||||
internal static void GetWriteModbusCommand(ref ValueByteBlock valueByteBlock, ModbusAddress mAddress, byte[] values, ushort length, ModbusTypeEnum modbusType, ushort sign, ushort protocolId)
|
||||
{
|
||||
if (modbusType == ModbusTypeEnum.ModbusTcp)
|
||||
{
|
||||
valueByteBlock.WriteUInt16(sign, EndianType.Big);
|
||||
valueByteBlock.WriteUInt16(protocolId, EndianType.Big);
|
||||
valueByteBlock.WriteUInt16(6, EndianType.Big);
|
||||
}
|
||||
valueByteBlock.WriteByte(mAddress.Station);
|
||||
valueByteBlock.WriteByte(16);
|
||||
valueByteBlock.WriteUInt16(mAddress.AddressStart, EndianType.Big);
|
||||
valueByteBlock.WriteUInt16(length, EndianType.Big);
|
||||
valueByteBlock.WriteByte((byte)values.Length);
|
||||
valueByteBlock.Write(values);
|
||||
|
||||
if (modbusType == ModbusTypeEnum.ModbusRtu)
|
||||
{
|
||||
valueByteBlock.Write(CRC16Utils.Crc16Only(valueByteBlock.Memory));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取6写入字报文
|
||||
/// </summary>
|
||||
internal static void GetWriteOneModbusCommand(ref ValueByteBlock valueByteBlock, ModbusAddress mAddress, byte[] values, ModbusTypeEnum modbusType, ushort sign, ushort protocolId)
|
||||
{
|
||||
if (modbusType == ModbusTypeEnum.ModbusTcp)
|
||||
{
|
||||
valueByteBlock.WriteUInt16(sign, EndianType.Big);
|
||||
valueByteBlock.WriteUInt16(protocolId, EndianType.Big);
|
||||
valueByteBlock.WriteUInt16(6, EndianType.Big);
|
||||
}
|
||||
valueByteBlock.WriteByte(mAddress.Station);
|
||||
valueByteBlock.WriteByte(6);
|
||||
valueByteBlock.WriteUInt16(mAddress.AddressStart, EndianType.Big);
|
||||
valueByteBlock.Write(values);
|
||||
|
||||
if (modbusType == ModbusTypeEnum.ModbusRtu)
|
||||
{
|
||||
valueByteBlock.Write(CRC16Utils.Crc16Only(valueByteBlock.Memory));
|
||||
}
|
||||
}
|
||||
|
||||
#endregion 报文构建
|
||||
}
|
||||
|
||||
@@ -11,22 +11,36 @@
|
||||
namespace ThingsGateway.Foundation.Modbus;
|
||||
|
||||
/// <summary>
|
||||
/// ModbusServerMessage
|
||||
/// <inheritdoc/>
|
||||
/// </summary>
|
||||
internal interface IModbusServerMessage : IResultMessage
|
||||
public class ModbusRequest
|
||||
{
|
||||
/// <summary>
|
||||
/// 读写长度
|
||||
/// </summary>
|
||||
int Length { get; set; }
|
||||
#region Request
|
||||
|
||||
/// <summary>
|
||||
/// 当前关联的地址
|
||||
/// 功能码
|
||||
/// </summary>
|
||||
ModbusAddress ModbusAddress { get; set; }
|
||||
public byte FunctionCode { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 当前关联的字节数组
|
||||
/// 站号
|
||||
/// </summary>
|
||||
ReadOnlyMemory<byte> Bytes { get; set; }
|
||||
public byte Station { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 起始位置
|
||||
/// </summary>
|
||||
public ushort StartAddress { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 读取字节数组长度
|
||||
/// </summary>
|
||||
public ushort Length { get; set; } = 1;
|
||||
|
||||
/// <summary>
|
||||
/// 数据
|
||||
/// </summary>
|
||||
public ReadOnlyMemory<byte> Data { get; set; }
|
||||
|
||||
#endregion Request
|
||||
}
|
||||
@@ -8,18 +8,15 @@
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace ThingsGateway.Foundation;
|
||||
namespace ThingsGateway.Foundation.Modbus;
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc/>
|
||||
public struct SendMessage : ISendMessage
|
||||
/// </summary>
|
||||
internal class ModbusResponse : ModbusRequest
|
||||
{
|
||||
public SendMessage(ReadOnlyMemory<byte> bytes)
|
||||
{
|
||||
SendBytes = bytes;
|
||||
}
|
||||
|
||||
public ReadOnlyMemory<byte> SendBytes { get; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public int Sign { get; set; }
|
||||
/// <summary>
|
||||
/// 错误码
|
||||
/// </summary>
|
||||
public byte? ErrorCode { get; set; }
|
||||
}
|
||||
@@ -59,7 +59,7 @@ public class PackHelper
|
||||
Dictionary<ModbusAddress, IVariable> map = group.ToDictionary((Func<IVariable, ModbusAddress>)(it =>
|
||||
{
|
||||
// 计算变量的字节长度
|
||||
var lastLen = it.DataType.GetByteLength();
|
||||
var lastLen = (int)it.DataType.GetByteLength();
|
||||
if (lastLen <= 0)
|
||||
{
|
||||
// 如果长度小于等于0,则根据数据类型进行处理
|
||||
@@ -67,7 +67,7 @@ public class PackHelper
|
||||
{
|
||||
case DataTypeEnum.String:
|
||||
// 字符串类型需特殊处理
|
||||
lastLen = it.ThingsGatewayBitConverter.StringLength == null ? throw new(DefaultResource.Localizer["StringTypePackError"]) : it.ThingsGatewayBitConverter.StringLength.Value;
|
||||
lastLen = (it.ThingsGatewayBitConverter.StringLength == null ? throw new(DefaultResource.Localizer["StringTypePackError"]) : it.ThingsGatewayBitConverter.StringLength.Value);
|
||||
break;
|
||||
|
||||
default:
|
||||
@@ -84,7 +84,7 @@ public class PackHelper
|
||||
// 解析地址,并设置字节长度
|
||||
var address = it.RegisterAddress;
|
||||
var result = ModbusAddress.ParseFrom(address, station, dtuid, isCache: false);
|
||||
result.ByteLength = lastLen;
|
||||
result.Length = (ushort)lastLen;
|
||||
return result;
|
||||
}));
|
||||
|
||||
@@ -96,11 +96,11 @@ public class PackHelper
|
||||
{
|
||||
var modbusAddressSameSocketIdList = modbusAddressList.Where(t => t.SocketId == socketId);
|
||||
// 获取所有功能码
|
||||
var functionCodes = modbusAddressSameSocketIdList.Select(t => t.ReadFunction).Distinct();
|
||||
var functionCodes = modbusAddressSameSocketIdList.Select(t => t.FunctionCode).Distinct();
|
||||
foreach (var functionCode in functionCodes)
|
||||
{
|
||||
// 获取相同功能码的地址
|
||||
var modbusAddressSameFunList = modbusAddressSameSocketIdList.Where(t => t.ReadFunction == functionCode);
|
||||
var modbusAddressSameFunList = modbusAddressSameSocketIdList.Where(t => t.FunctionCode == functionCode);
|
||||
// 获取相同站号的地址
|
||||
var stationNumbers = modbusAddressSameFunList.Select(t => t.Station).Distinct();
|
||||
foreach (var stationNumber in stationNumbers)
|
||||
@@ -129,11 +129,11 @@ public class PackHelper
|
||||
// 按地址结束位置排序
|
||||
var orderByAddressEnd = addressList.Keys.OrderBy(it => it.AddressEnd);
|
||||
// 按地址开始位置排序
|
||||
var orderByAddressStart = addressList.Keys.OrderBy(it => it.AddressStart);
|
||||
var orderByAddressStart = addressList.Keys.OrderBy(it => it.StartAddress);
|
||||
// 获取地址最小值
|
||||
var minAddress = orderByAddressStart.First().AddressStart;
|
||||
var minAddress = orderByAddressStart.First().StartAddress;
|
||||
// 获取地址最大值
|
||||
var maxAddress = orderByAddressStart.Last().AddressStart;
|
||||
var maxAddress = orderByAddressStart.Last().StartAddress;
|
||||
|
||||
// 循环直到处理完所有地址
|
||||
while (maxAddress >= minAddress)
|
||||
@@ -149,9 +149,9 @@ public class PackHelper
|
||||
// 获取当前一组打包地址信息,使得地址不超过读取长度
|
||||
var tempAddressEnd = orderByAddressEnd.Where(t => t.AddressEnd <= minAddress + readLength);
|
||||
// 获取起始地址(当前组打包地址中起始地址最小的)
|
||||
var startAddress = tempAddressEnd.OrderBy(it => it.AddressStart).First();
|
||||
var startAddress = tempAddressEnd.OrderBy(it => it.StartAddress).First();
|
||||
// 计算读取寄存器长度
|
||||
var sourceLen = tempAddressEnd.Last().AddressEnd - startAddress.AddressStart;
|
||||
var sourceLen = tempAddressEnd.Last().AddressEnd - startAddress.StartAddress;
|
||||
|
||||
// 创建一个新的变量源读取对象
|
||||
T sourceRead = new()
|
||||
@@ -169,15 +169,15 @@ public class PackHelper
|
||||
// 如果功能码为 -1、3 或 4,并且数据类型为布尔型,则根据位偏移计算索引
|
||||
if ((functionCode == -1 || functionCode == 3 || functionCode == 4) && readNode.DataType == DataTypeEnum.Boolean)
|
||||
{
|
||||
readNode.Index = ((item.AddressStart - startAddress.AddressStart) * 16) + readNode.Index;
|
||||
readNode.Index = ((item.StartAddress - startAddress.StartAddress) * 16) + readNode.Index;
|
||||
}
|
||||
else
|
||||
{
|
||||
// 其他情况根据功能码和地址计算索引
|
||||
if (functionCode == 1 || functionCode == 2)
|
||||
readNode.Index = item.AddressStart - startAddress.AddressStart + readNode.Index;
|
||||
readNode.Index = item.StartAddress - startAddress.StartAddress + readNode.Index;
|
||||
else
|
||||
readNode.Index = ((item.AddressStart - startAddress.AddressStart) * 2) + readNode.Index;
|
||||
readNode.Index = ((item.StartAddress - startAddress.StartAddress) * 2) + readNode.Index;
|
||||
}
|
||||
|
||||
// 将读取节点添加到变量源读取对象中,并从地址列表中移除
|
||||
@@ -190,7 +190,7 @@ public class PackHelper
|
||||
|
||||
// 更新最小地址值,如果还有地址未处理,则继续循环;否则跳出循环
|
||||
if (orderByAddressEnd.Count() > 0)
|
||||
minAddress = orderByAddressStart.First().AddressStart;
|
||||
minAddress = orderByAddressStart.First().StartAddress;
|
||||
else
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://kimdiego2098.github.io/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace ThingsGateway.Foundation.Modbus;
|
||||
|
||||
/// <summary>
|
||||
/// Rtu适配器
|
||||
/// </summary>
|
||||
internal class ModbusRtuDataHandleAdapter : ReadWriteDevicesSingleStreamDataHandleAdapter<ModbusRtuMessage>
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
protected override AdapterResult UnpackResponse(ModbusRtuMessage request, IByteBlock byteBlock)
|
||||
{
|
||||
var result = ModbusHelper.GetModbusRtuData(request.SendBytes.Span, byteBlock);
|
||||
request.OperCode = result.OperCode;
|
||||
request.ErrorMessage = result.ErrorMessage;
|
||||
return result.Content;
|
||||
}
|
||||
}
|
||||
@@ -16,55 +16,124 @@ namespace ThingsGateway.Foundation.Modbus;
|
||||
internal class ModbusRtuMessage : MessageBase, IResultMessage
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public override int HeadBytesLength => 3;
|
||||
public override int HeaderLength => 3;
|
||||
|
||||
public ReadOnlyMemory<byte> SendBytes { get; set; }
|
||||
public ModbusAddress? Request { get; set; }
|
||||
|
||||
public override void SendInfo(ReadOnlyMemory<byte> sendBytes)
|
||||
public ModbusResponse Response { get; set; } = new();
|
||||
|
||||
public override void SendInfo(ISendMessage sendMessage)
|
||||
{
|
||||
SendBytes = sendBytes;
|
||||
Request = (sendMessage as ModbusRtuSend).ModbusAddress;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool CheckHeadBytes(byte[]? headBytes)
|
||||
public override bool CheckHead<TByteBlock>(ref TByteBlock byteBlock)
|
||||
{
|
||||
if (headBytes == null || headBytes.Length <= 0) return false;
|
||||
//01 03 02 00 01 xx xx
|
||||
//01 04 02 00 01 xx xx
|
||||
//01 01 02 00 01 xx xx
|
||||
//01 02 02 00 01 xx xx
|
||||
//01 05 00 00 00 00 xx xx
|
||||
//01 06 00 00 00 00 xx xx
|
||||
//01 0f 00 00 00 01 xx xx
|
||||
//01 10 00 00 00 01 xx xx
|
||||
|
||||
//modbusRtu 读取
|
||||
if (headBytes[1] <= 0x04)
|
||||
Response.Station = byteBlock.ReadByte();
|
||||
bool error = false;
|
||||
var code = byteBlock.ReadByte();
|
||||
if ((code & 0x80) == 0)
|
||||
{
|
||||
int num = (headBytes[2]);
|
||||
if (num > 0xff - 4) return false;
|
||||
BodyLength = num + 2; //数据区+crc
|
||||
Response.FunctionCode = code;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (headBytes[1] <= 0x10)
|
||||
{
|
||||
//modbusRtu 写入
|
||||
BodyLength = 3 + 2; //数据区+crc
|
||||
}
|
||||
else
|
||||
{
|
||||
BodyLength = 0 + 2; //数据区+crc
|
||||
}
|
||||
code = code.SetBit(7, false);
|
||||
Response.FunctionCode = code;
|
||||
error = true;
|
||||
}
|
||||
|
||||
if (SendBytes.Length > 0)
|
||||
if (error)
|
||||
{
|
||||
Response.ErrorCode = byteBlock.ReadByte();
|
||||
this.OperCode = Response.ErrorCode;
|
||||
this.ErrorMessage = ModbusHelper.GetDescriptionByErrorCode(Response.ErrorCode.Value);
|
||||
BodyLength = 2;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;//不是主动请求的,直接放弃
|
||||
//验证发送/返回站号与功能码
|
||||
//站号验证
|
||||
if (Request.Station != Response.Station)
|
||||
{
|
||||
this.OperCode = -1;
|
||||
Response.ErrorCode = 1;
|
||||
this.ErrorMessage = ModbusResource.Localizer["StationNotSame", Request.Station, Response.Station];
|
||||
return true;
|
||||
}
|
||||
if (Response.FunctionCode > 4 ? Request.WriteFunctionCode != Response.FunctionCode : Request.FunctionCode != Response.FunctionCode)
|
||||
{
|
||||
this.OperCode = -1;
|
||||
Response.ErrorCode = 1;
|
||||
this.ErrorMessage = ModbusResource.Localizer["FunctionNotSame", Request.FunctionCode, Response.FunctionCode];
|
||||
return true;
|
||||
}
|
||||
|
||||
if (Response.FunctionCode == 5 || Response.FunctionCode == 6)
|
||||
{
|
||||
BodyLength = 5;
|
||||
return true;
|
||||
}
|
||||
else if (Response.FunctionCode == 15 || Response.FunctionCode == 16)
|
||||
{
|
||||
BodyLength = 5;
|
||||
return true;
|
||||
}
|
||||
else if (Response.FunctionCode <= 4)
|
||||
{
|
||||
Response.Length = byteBlock.ReadByte();
|
||||
BodyLength = Response.Length + 2; //数据区+crc
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public override FilterResult CheckBody<TByteBlock>(ref TByteBlock byteBlock)
|
||||
{
|
||||
if (Response.ErrorCode.HasValue)
|
||||
{
|
||||
return FilterResult.Success;
|
||||
}
|
||||
var pos = byteBlock.Position - HeaderLength;
|
||||
var crcLen = 0;
|
||||
|
||||
if (Response.FunctionCode <= 4)
|
||||
{
|
||||
this.Content = byteBlock.ToArrayTake(BodyLength - 2);
|
||||
Response.Data = this.Content;
|
||||
crcLen = 3 + Response.Length;
|
||||
}
|
||||
else if (Response.FunctionCode == 5 || Response.FunctionCode == 6)
|
||||
{
|
||||
byteBlock.Position = HeaderLength - 1;
|
||||
Response.StartAddress = byteBlock.ReadUInt16(EndianType.Big);
|
||||
this.Content = byteBlock.ToArrayTake(BodyLength - 4);
|
||||
Response.Data = this.Content;
|
||||
crcLen = 6;
|
||||
}
|
||||
else if (Response.FunctionCode == 15 || Response.FunctionCode == 16)
|
||||
{
|
||||
byteBlock.Position = HeaderLength - 1;
|
||||
Response.StartAddress = byteBlock.ReadUInt16(EndianType.Big);
|
||||
Response.Length = byteBlock.ReadUInt16(EndianType.Big);
|
||||
this.Content = Array.Empty<byte>();
|
||||
crcLen = 6;
|
||||
}
|
||||
|
||||
if (crcLen > 0)
|
||||
{
|
||||
var crc = CRC16Utils.Crc16Only(byteBlock.Span.Slice(pos, crcLen));
|
||||
|
||||
//Crc
|
||||
var checkCrc = byteBlock.Span.Slice(pos + crcLen, 2).ToArray();
|
||||
if (crc.SequenceEqual(checkCrc))
|
||||
{
|
||||
return FilterResult.Success;
|
||||
}
|
||||
}
|
||||
return FilterResult.GoOn;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://kimdiego2098.github.io/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace ThingsGateway.Foundation.Modbus;
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc/>
|
||||
/// </summary>
|
||||
internal class ModbusRtuOverUdpDataHandleAdapter : ReadWriteDevicesUdpDataHandleAdapter<ModbusRtuMessage>
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
protected override AdapterResult UnpackResponse(ModbusRtuMessage request, IByteBlock byteBlock)
|
||||
{
|
||||
var result = ModbusHelper.GetModbusRtuData(request.SendBytes.Span, byteBlock);
|
||||
request.OperCode = result.OperCode;
|
||||
request.ErrorMessage = result.ErrorMessage;
|
||||
return result.Content;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://kimdiego2098.github.io/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using TouchSocket.Sockets;
|
||||
|
||||
namespace ThingsGateway.Foundation.Modbus;
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc/>
|
||||
/// </summary>
|
||||
internal class ModbusRtuSend : ISendMessage
|
||||
{
|
||||
public int Sign { get; set; }
|
||||
|
||||
public int MaxLength => 300;
|
||||
|
||||
internal ModbusAddress ModbusAddress { get; }
|
||||
private bool Read;
|
||||
|
||||
public ModbusRtuSend(ModbusAddress modbusAddress, ushort sign, bool read)
|
||||
{
|
||||
Sign = sign;
|
||||
ModbusAddress = modbusAddress;
|
||||
Read = read;
|
||||
}
|
||||
|
||||
public void Build<TByteBlock>(ref TByteBlock byteBlock) where TByteBlock : IByteBlock
|
||||
{
|
||||
if (!Read)
|
||||
{
|
||||
if (ModbusAddress.WriteFunctionCode == null)
|
||||
{
|
||||
ModbusAddress.WriteFunctionCode = (byte)(ModbusAddress.FunctionCode == 1 ? 5 : 6);
|
||||
}
|
||||
if (ModbusAddress.Data.Length > 2 && ModbusAddress.WriteFunctionCode < 15)
|
||||
{
|
||||
ModbusAddress.WriteFunctionCode = (byte)(ModbusAddress.FunctionCode == 1 ? 15 : 16);
|
||||
}
|
||||
}
|
||||
|
||||
var f = Read ? ModbusAddress.FunctionCode : ModbusAddress.WriteFunctionCode;
|
||||
|
||||
if (f <= 4)
|
||||
{
|
||||
byteBlock.WriteByte(ModbusAddress.Station);
|
||||
byteBlock.WriteByte((byte)f);
|
||||
byteBlock.WriteUInt16(ModbusAddress.StartAddress, EndianType.Big);
|
||||
byteBlock.WriteUInt16(ModbusAddress.Length, EndianType.Big);
|
||||
}
|
||||
else if (f == 5 || f == 6)
|
||||
{
|
||||
byteBlock.WriteByte(ModbusAddress.Station);
|
||||
byteBlock.WriteByte((byte)f);
|
||||
byteBlock.WriteUInt16(ModbusAddress.StartAddress, EndianType.Big);
|
||||
byteBlock.Write(ModbusAddress.Data.Span);
|
||||
}
|
||||
else if (f == 15 || f == 16)
|
||||
{
|
||||
byteBlock.WriteByte(ModbusAddress.Station);
|
||||
byteBlock.WriteByte((byte)f);
|
||||
byteBlock.WriteUInt16(ModbusAddress.StartAddress, EndianType.Big);
|
||||
byteBlock.WriteUInt16((ushort)Math.Ceiling(ModbusAddress.Data.Length / 2.0), EndianType.Big);
|
||||
byteBlock.WriteByte((byte)ModbusAddress.Data.Length);
|
||||
byteBlock.Write(ModbusAddress.Data.Span);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new System.InvalidOperationException(ModbusResource.Localizer["ModbusError1"]);
|
||||
}
|
||||
byteBlock.Write(CRC16Utils.Crc16Only(byteBlock.Span));
|
||||
}
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://kimdiego2098.github.io/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace ThingsGateway.Foundation.Modbus;
|
||||
|
||||
/// <summary>
|
||||
/// ModbusTcpDataHandleAdapter
|
||||
/// </summary>
|
||||
internal class ModbusTcpDataHandleAdapter : ReadWriteDevicesSingleStreamDataHandleAdapter<ModbusTcpMessage>
|
||||
{
|
||||
public ModbusTcpDataHandleAdapter()
|
||||
{
|
||||
this.MaxPackageSize = 1024 * 1024 * 1024;
|
||||
}
|
||||
|
||||
public override bool IsSingleThread { get; } = false;
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override AdapterResult UnpackResponse(ModbusTcpMessage request, IByteBlock byteBlock)
|
||||
{
|
||||
var pos = byteBlock.Position;
|
||||
byteBlock.Position = byteBlock.Position + 6;
|
||||
var result = ModbusHelper.GetModbusData(null, byteBlock);
|
||||
|
||||
request.OperCode = result.OperCode;
|
||||
request.ErrorMessage = result.ErrorMessage;
|
||||
|
||||
byteBlock.Position = pos;
|
||||
request.Sign = byteBlock.ReadUInt16(EndianType.Big);
|
||||
return result.Content;
|
||||
}
|
||||
}
|
||||
@@ -16,17 +16,79 @@ namespace ThingsGateway.Foundation.Modbus;
|
||||
internal class ModbusTcpMessage : MessageBase, IResultMessage
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public override int HeadBytesLength => 6;
|
||||
public override int HeaderLength => 9;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool CheckHeadBytes(byte[]? headBytes)
|
||||
public ModbusResponse Response { get; set; } = new();
|
||||
|
||||
public override bool CheckHead<TByteBlock>(ref TByteBlock byteBlock)
|
||||
{
|
||||
if (headBytes == null || headBytes.Length <= 0) return false;
|
||||
this.Sign = byteBlock.ReadUInt16(EndianType.Big);
|
||||
byteBlock.Position += 2;
|
||||
this.BodyLength = byteBlock.ReadUInt16(EndianType.Big) - 3;
|
||||
Response.Station = byteBlock.ReadByte();
|
||||
bool error = false;
|
||||
var code = byteBlock.ReadByte();
|
||||
if ((code & 0x80) == 0)
|
||||
{
|
||||
Response.FunctionCode = code;
|
||||
}
|
||||
else
|
||||
{
|
||||
code = code.SetBit(7, false);
|
||||
Response.FunctionCode = code;
|
||||
error = true;
|
||||
}
|
||||
|
||||
int num = (headBytes[4] * 256) + headBytes[5];
|
||||
if (num > 0xff + 3) return false;
|
||||
BodyLength = num;
|
||||
if (error)
|
||||
{
|
||||
Response.ErrorCode = byteBlock.ReadByte();
|
||||
this.OperCode = Response.ErrorCode;
|
||||
this.ErrorMessage = ModbusHelper.GetDescriptionByErrorCode(Response.ErrorCode.Value);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (Response.FunctionCode <= 4)
|
||||
{
|
||||
Response.Length = byteBlock.ReadByte();
|
||||
}
|
||||
else
|
||||
{
|
||||
Response.StartAddress = byteBlock.ReadUInt16();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
public override FilterResult CheckBody<TByteBlock>(ref TByteBlock byteBlock)
|
||||
{
|
||||
if (Response.ErrorCode.HasValue)
|
||||
{
|
||||
return FilterResult.Success;
|
||||
}
|
||||
|
||||
if (Response.FunctionCode <= 4)
|
||||
{
|
||||
this.Content = byteBlock.ToArrayTake(BodyLength);
|
||||
Response.Data = this.Content;
|
||||
return FilterResult.Success;
|
||||
}
|
||||
else if (Response.FunctionCode == 5 || Response.FunctionCode == 6)
|
||||
{
|
||||
byteBlock.Position = HeaderLength - 1;
|
||||
Response.StartAddress = byteBlock.ReadUInt16();
|
||||
this.Content = byteBlock.ToArrayTake(BodyLength - 1);
|
||||
Response.Data = this.Content;
|
||||
return FilterResult.Success;
|
||||
}
|
||||
else if (Response.FunctionCode == 15 || Response.FunctionCode == 16)
|
||||
{
|
||||
byteBlock.Position = HeaderLength - 1;
|
||||
Response.StartAddress = byteBlock.ReadUInt16(EndianType.Big);
|
||||
Response.Length = byteBlock.ReadUInt16(EndianType.Big);
|
||||
this.Content = Array.Empty<byte>();
|
||||
return FilterResult.Success;
|
||||
}
|
||||
return FilterResult.GoOn;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://kimdiego2098.github.io/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace ThingsGateway.Foundation.Modbus;
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc/>
|
||||
/// </summary>
|
||||
internal class ModbusTcpSend : ISendMessage
|
||||
{
|
||||
/// <summary>
|
||||
/// 事务处理标识符。即序号
|
||||
/// </summary>
|
||||
public ushort TransactionId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 协议标识符
|
||||
/// </summary>
|
||||
public ushort ProtocolId { get; set; }
|
||||
|
||||
public int Sign { get; set; }
|
||||
|
||||
public int MaxLength => 300;
|
||||
|
||||
private ModbusAddress ModbusAddress;
|
||||
private bool Read;
|
||||
|
||||
public ModbusTcpSend(ModbusAddress modbusAddress, ushort transactionId, bool read, ushort protocolId = 0)
|
||||
{
|
||||
TransactionId = transactionId;
|
||||
Sign = transactionId;
|
||||
ProtocolId = protocolId;
|
||||
ModbusAddress = modbusAddress;
|
||||
Read = read;
|
||||
}
|
||||
|
||||
public void Build<TByteBlock>(ref TByteBlock byteBlock) where TByteBlock : IByteBlock
|
||||
{
|
||||
if (!Read)
|
||||
{
|
||||
if (ModbusAddress.WriteFunctionCode == null)
|
||||
{
|
||||
ModbusAddress.WriteFunctionCode = (byte)(ModbusAddress.FunctionCode == 1 ? 5 : 6);
|
||||
}
|
||||
if (ModbusAddress.Data.Length > 2 && ModbusAddress.WriteFunctionCode < 15)
|
||||
{
|
||||
ModbusAddress.WriteFunctionCode = (byte)(ModbusAddress.FunctionCode == 1 ? 15 : 16);
|
||||
}
|
||||
}
|
||||
var f = Read ? ModbusAddress.FunctionCode : ModbusAddress.WriteFunctionCode;
|
||||
|
||||
byteBlock.WriteUInt16(TransactionId, EndianType.Big);
|
||||
byteBlock.WriteUInt16(ProtocolId, EndianType.Big);
|
||||
if (f <= 4)
|
||||
{
|
||||
byteBlock.WriteUInt16((ushort)6, EndianType.Big);
|
||||
byteBlock.WriteByte(ModbusAddress.Station);
|
||||
byteBlock.WriteByte((byte)f);
|
||||
byteBlock.WriteUInt16(ModbusAddress.StartAddress, EndianType.Big);
|
||||
byteBlock.WriteUInt16(ModbusAddress.Length, EndianType.Big);
|
||||
}
|
||||
else if (f == 5 || f == 6)
|
||||
{
|
||||
byteBlock.WriteUInt16((ushort)6, EndianType.Big);
|
||||
byteBlock.WriteByte(ModbusAddress.Station);
|
||||
byteBlock.WriteByte((byte)f);
|
||||
byteBlock.WriteUInt16(ModbusAddress.StartAddress, EndianType.Big);
|
||||
byteBlock.Write(ModbusAddress.Data.Span);
|
||||
}
|
||||
else if (f == 15 || f == 16)
|
||||
{
|
||||
byteBlock.WriteUInt16((ushort)(ModbusAddress.Data.Length + 7), EndianType.Big);
|
||||
byteBlock.WriteByte(ModbusAddress.Station);
|
||||
byteBlock.WriteByte((byte)f);
|
||||
byteBlock.WriteUInt16(ModbusAddress.StartAddress, EndianType.Big);
|
||||
byteBlock.WriteUInt16((ushort)Math.Ceiling(ModbusAddress.Data.Length / 2.0), EndianType.Big);
|
||||
byteBlock.WriteByte((byte)ModbusAddress.Data.Length);
|
||||
byteBlock.Write(ModbusAddress.Data.Span);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new System.InvalidOperationException(ModbusResource.Localizer["ModbusError1"]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://kimdiego2098.github.io/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace ThingsGateway.Foundation.Modbus;
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc/>
|
||||
/// </summary>
|
||||
internal class ModbusUdpDataHandleAdapter : ReadWriteDevicesUdpDataHandleAdapter<ModbusTcpMessage>
|
||||
{
|
||||
public override bool IsSingleThread { get; } = false;
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override AdapterResult UnpackResponse(ModbusTcpMessage request, IByteBlock byteBlock)
|
||||
{
|
||||
var pos = byteBlock.Position;
|
||||
byteBlock.Position = byteBlock.Position + 6;
|
||||
var result = ModbusHelper.GetModbusData(null, byteBlock);
|
||||
request.OperCode = result.OperCode;
|
||||
request.ErrorMessage = result.ErrorMessage;
|
||||
|
||||
byteBlock.Position = pos;
|
||||
request.Sign = byteBlock.ReadUInt16(EndianType.Big);
|
||||
return result.Content;
|
||||
}
|
||||
}
|
||||
@@ -8,8 +8,6 @@
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using ThingsGateway.Foundation.Extension.Generic;
|
||||
|
||||
namespace ThingsGateway.Foundation.Modbus;
|
||||
|
||||
/// <inheritdoc/>
|
||||
@@ -26,6 +24,34 @@ public partial class ModbusMaster : ProtocolBase, IDtu
|
||||
client.WaitHandlePool.MaxSign = ushort.MaxValue;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 客户端连接滑动过期时间(TCP服务通道时)
|
||||
/// </summary>
|
||||
public int CheckClearTime { get; set; } = 120;
|
||||
|
||||
/// <summary>
|
||||
/// 默认Dtu注册包,utf-8字符串
|
||||
/// </summary>
|
||||
public string DtuId { get; set; } = "DtuId";
|
||||
|
||||
/// <summary>
|
||||
/// 心跳检测(大写16进制字符串)
|
||||
/// </summary>
|
||||
public string HeartbeatHexString { get; set; } = "FFFF8080";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool IsSingleThread
|
||||
{
|
||||
get
|
||||
{
|
||||
switch (ModbusType)
|
||||
{
|
||||
case ModbusTypeEnum.ModbusTcp: return false;
|
||||
default: return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Modbus类型
|
||||
/// </summary>
|
||||
@@ -46,45 +72,11 @@ public partial class ModbusMaster : ProtocolBase, IDtu
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool IsSingleThread
|
||||
{
|
||||
get
|
||||
{
|
||||
switch (ModbusType)
|
||||
{
|
||||
case ModbusTypeEnum.ModbusTcp: return false;
|
||||
default: return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 站号
|
||||
/// </summary>
|
||||
public byte Station { get; set; } = 1;
|
||||
|
||||
/// <summary>
|
||||
/// 默认Dtu注册包,utf-8字符串
|
||||
/// </summary>
|
||||
public string DtuId { get; set; } = "TEST";
|
||||
|
||||
/// <summary>
|
||||
/// 客户端连接滑动过期时间(TCP服务通道时)
|
||||
/// </summary>
|
||||
public int CheckClearTime { get; set; } = 120;
|
||||
|
||||
/// <summary>
|
||||
/// 心跳检测(大写16进制字符串)
|
||||
/// </summary>
|
||||
public string HeartbeatHexString { get; set; } = "FFFF8080";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string GetAddressDescription()
|
||||
{
|
||||
return $"{base.GetAddressDescription()}{Environment.NewLine}{ModbusHelper.GetAddressDescription()}";
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override Action<IPluginManager> ConfigurePlugins()
|
||||
{
|
||||
@@ -96,6 +88,12 @@ public partial class ModbusMaster : ProtocolBase, IDtu
|
||||
return base.ConfigurePlugins();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string GetAddressDescription()
|
||||
{
|
||||
return $"{base.GetAddressDescription()}{Environment.NewLine}{ModbusHelper.GetAddressDescription()}";
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override DataHandlingAdapter GetDataAdapter()
|
||||
{
|
||||
@@ -107,13 +105,13 @@ public partial class ModbusMaster : ProtocolBase, IDtu
|
||||
case ChannelTypeEnum.TcpClient:
|
||||
case ChannelTypeEnum.TcpService:
|
||||
case ChannelTypeEnum.SerialPort:
|
||||
return new ModbusTcpDataHandleAdapter()
|
||||
return new ProtocolSingleStreamDataHandleAdapter<ModbusTcpMessage>()
|
||||
{
|
||||
CacheTimeout = TimeSpan.FromMilliseconds(CacheTimeout)
|
||||
};
|
||||
|
||||
case ChannelTypeEnum.UdpSession:
|
||||
return new ModbusUdpDataHandleAdapter()
|
||||
return new ProtocolUdpDataHandleAdapter<ModbusTcpMessage>()
|
||||
{
|
||||
};
|
||||
}
|
||||
@@ -125,19 +123,19 @@ public partial class ModbusMaster : ProtocolBase, IDtu
|
||||
case ChannelTypeEnum.TcpClient:
|
||||
case ChannelTypeEnum.TcpService:
|
||||
case ChannelTypeEnum.SerialPort:
|
||||
return new ModbusRtuDataHandleAdapter()
|
||||
return new ProtocolSingleStreamDataHandleAdapter<ModbusRtuMessage>()
|
||||
{
|
||||
CacheTimeout = TimeSpan.FromMilliseconds(CacheTimeout)
|
||||
};
|
||||
|
||||
case ChannelTypeEnum.UdpSession:
|
||||
return new ModbusRtuOverUdpDataHandleAdapter()
|
||||
return new ProtocolUdpDataHandleAdapter<ModbusRtuMessage>()
|
||||
{
|
||||
};
|
||||
}
|
||||
break;
|
||||
}
|
||||
return new ModbusTcpDataHandleAdapter()
|
||||
return new ProtocolSingleStreamDataHandleAdapter<ModbusTcpMessage>()
|
||||
{
|
||||
CacheTimeout = TimeSpan.FromMilliseconds(CacheTimeout)
|
||||
};
|
||||
@@ -149,25 +147,16 @@ public partial class ModbusMaster : ProtocolBase, IDtu
|
||||
return PackHelper.LoadSourceRead<T>(this, deviceVariables, maxPack, defaultIntervalTime, Station, DtuId);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override async ValueTask<OperResult<byte[]>> ReadAsync(string address, int length, CancellationToken cancellationToken = default)
|
||||
public async ValueTask<OperResult<byte[]>> ModbusRequestAsync(ModbusAddress mAddress, bool read, CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
var mAddress = ModbusAddress.ParseFrom(address, Station, DtuId);
|
||||
var channelResult = GetChannel(mAddress.SocketId);
|
||||
if (!channelResult.IsSuccess) return new OperResult<byte[]>(channelResult);
|
||||
ValueByteBlock valueByteBlock = new ValueByteBlock(1024);
|
||||
try
|
||||
{
|
||||
var waitData = channelResult.Content.WaitHandlePool.GetWaitDataAsync(out var sign);
|
||||
ModbusHelper.GetReadModbusCommand(ref valueByteBlock, mAddress, (ushort)length, ModbusType, (ushort)sign, 0);
|
||||
return await this.SendThenReturnAsync(new SendMessage(valueByteBlock.Memory) { Sign = sign }, waitData, cancellationToken, channelResult.Content).ConfigureAwait(false);
|
||||
}
|
||||
finally
|
||||
{
|
||||
valueByteBlock.SafeDispose();
|
||||
}
|
||||
var waitData = channelResult.Content.WaitHandlePool.GetWaitDataAsync(out var sign);
|
||||
return await this.SendThenReturnAsync(
|
||||
GetSendMessage(mAddress, (ushort)sign, read),
|
||||
channelResult.Content, waitData, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -175,31 +164,29 @@ public partial class ModbusMaster : ProtocolBase, IDtu
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override ValueTask<OperResult<byte[]>> ReadAsync(string address, int length, CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
var mAddress = ModbusAddress.ParseFrom(address, Station, DtuId);
|
||||
mAddress.Length = (ushort)length;
|
||||
return ModbusRequestAsync(mAddress, true, cancellationToken);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return EasyValueTask.FromResult(new OperResult<byte[]>(ex));
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override async ValueTask<OperResult> WriteAsync(string address, byte[] value, CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
var mAddress = ModbusAddress.ParseFrom(address, Station, DtuId);
|
||||
value = value.ArrayExpandToLengthEven();
|
||||
|
||||
var channelResult = GetChannel(mAddress.SocketId);
|
||||
if (!channelResult.IsSuccess) return new OperResult<byte[]>(channelResult);
|
||||
ValueByteBlock valueByteBlock = new ValueByteBlock(1024);
|
||||
try
|
||||
{
|
||||
var waitData = channelResult.Content.WaitHandlePool.GetWaitDataAsync(out var sign);
|
||||
if (value.Length > 2 || mAddress.WriteFunction == 16)
|
||||
ModbusHelper.GetWriteModbusCommand(ref valueByteBlock, mAddress, value, (ushort)(value.Length / RegisterByteLength), ModbusType, (ushort)sign, 0);
|
||||
else
|
||||
ModbusHelper.GetWriteOneModbusCommand(ref valueByteBlock, mAddress, value, ModbusType, (ushort)sign, 0);
|
||||
|
||||
return await this.SendThenReturnAsync(new SendMessage(valueByteBlock.Memory) { Sign = sign }, waitData, cancellationToken, channelResult.Content).ConfigureAwait(false);
|
||||
}
|
||||
finally
|
||||
{
|
||||
valueByteBlock.SafeDispose();
|
||||
}
|
||||
mAddress.Data = value;
|
||||
return await ModbusRequestAsync(mAddress, false, cancellationToken);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -213,53 +200,32 @@ public partial class ModbusMaster : ProtocolBase, IDtu
|
||||
try
|
||||
{
|
||||
var mAddress = ModbusAddress.ParseFrom(address, Station, DtuId);
|
||||
var channelResult = GetChannel(mAddress.SocketId);
|
||||
if (!channelResult.IsSuccess) return new OperResult<byte[]>(channelResult);
|
||||
ValueByteBlock valueByteBlock = new ValueByteBlock(1024);
|
||||
try
|
||||
if (value.Length > 1 || mAddress.WriteFunctionCode == 15)
|
||||
{
|
||||
var waitData = channelResult.Content.WaitHandlePool.GetWaitDataAsync(out var sign);
|
||||
if (value.Length > 1 || mAddress.WriteFunction == 15)
|
||||
mAddress.Data = value.BoolArrayToByte();
|
||||
return await ModbusRequestAsync(mAddress, false, cancellationToken);
|
||||
}
|
||||
else if (mAddress.BitIndex == null)
|
||||
{
|
||||
mAddress.Data = value[0] ? new byte[2] { 255, 0 } : [0, 0];
|
||||
return await ModbusRequestAsync(mAddress, false, cancellationToken);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (mAddress.BitIndex < 16)
|
||||
{
|
||||
ModbusHelper.GetWriteBoolModbusCommand(ref valueByteBlock, mAddress, value, (ushort)value.Length, ModbusType, (ushort)sign, 0);
|
||||
var readData = await ModbusRequestAsync(mAddress, true, cancellationToken);
|
||||
if (!readData.IsSuccess) return readData;
|
||||
var writeData = ThingsGatewayBitConverter.ToUInt16(readData.Content, 0);
|
||||
ushort mask = (ushort)(1 << mAddress.BitIndex);
|
||||
ushort result = (ushort)(value[0] ? (writeData | mask) : (writeData & ~mask));
|
||||
mAddress.Data = ThingsGatewayBitConverter.GetBytes(result);
|
||||
return await ModbusRequestAsync(mAddress, false, cancellationToken);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (mAddress.BitIndex == null)
|
||||
{
|
||||
ModbusHelper.GetWriteBoolModbusCommand(ref valueByteBlock, mAddress, value[0], ModbusType, (ushort)sign, 0);
|
||||
}
|
||||
else if (mAddress.BitIndex < 16)
|
||||
{
|
||||
ValueByteBlock readValueByteBlock = new ValueByteBlock(1024);
|
||||
try
|
||||
{
|
||||
var readWaitData = channelResult.Content.WaitHandlePool.GetWaitDataAsync(out var readSign);
|
||||
//比如40001.1
|
||||
ModbusHelper.GetReadModbusCommand(ref readValueByteBlock, mAddress, 1, ModbusType, (ushort)readSign, 0);
|
||||
var readData = await this.SendThenReturnAsync(new SendMessage(readValueByteBlock.Memory) { Sign = readSign }, readWaitData, cancellationToken, channelResult.Content).ConfigureAwait(false);
|
||||
if (!readData.IsSuccess) return readData;
|
||||
var writeData = ThingsGatewayBitConverter.ToUInt16(readData.Content, 0);
|
||||
ushort mask = (ushort)(1 << mAddress.BitIndex);
|
||||
ushort result = (ushort)(value[0] ? (writeData | mask) : (writeData & ~mask));
|
||||
ModbusHelper.GetWriteOneModbusCommand(ref valueByteBlock, mAddress, ThingsGatewayBitConverter.GetBytes(result), ModbusType, (ushort)sign, 0);
|
||||
}
|
||||
finally
|
||||
{
|
||||
readValueByteBlock.SafeDispose();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return new OperResult(ModbusResource.Localizer["ValueOverlimit", nameof(mAddress.BitIndex), 16]);
|
||||
}
|
||||
return new OperResult(ModbusResource.Localizer["ValueOverlimit", nameof(mAddress.BitIndex), 16]);
|
||||
}
|
||||
|
||||
return await this.SendThenReturnAsync(new SendMessage(valueByteBlock.Memory) { Sign = sign }, waitData, cancellationToken, channelResult.Content).ConfigureAwait(false);
|
||||
}
|
||||
finally
|
||||
{
|
||||
valueByteBlock.SafeDispose();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -267,4 +233,16 @@ public partial class ModbusMaster : ProtocolBase, IDtu
|
||||
return new OperResult(ex);
|
||||
}
|
||||
}
|
||||
|
||||
private ISendMessage GetSendMessage(ModbusAddress modbusAddress, ushort sign, bool read)
|
||||
{
|
||||
if (ModbusType == ModbusTypeEnum.ModbusRtu)
|
||||
{
|
||||
return new ModbusRtuSend(modbusAddress, sign, read);
|
||||
}
|
||||
else
|
||||
{
|
||||
return new ModbusTcpSend(modbusAddress, sign, read);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://kimdiego2098.github.io/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace ThingsGateway.Foundation.Modbus;
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc/>
|
||||
/// </summary>
|
||||
internal class ModbusRtuOverUdpServerDataHandleAdapter : ReadWriteDevicesUdpDataHandleAdapter<ModbusRtuServerMessage>
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
protected override AdapterResult UnpackResponse(ModbusRtuServerMessage request, IByteBlock byteBlock)
|
||||
{
|
||||
var pos = byteBlock.Position;
|
||||
var result = ModbusHelper.CheckCrc(byteBlock);
|
||||
request.OperCode = result.OperCode;
|
||||
request.ErrorMessage = result.ErrorMessage;
|
||||
if (result.IsSuccess)
|
||||
{
|
||||
byteBlock.Position = pos;
|
||||
var bytes = ModbusHelper.ModbusServerAnalysisAddressValue(request, byteBlock);
|
||||
request.Bytes = byteBlock.AsSegment(pos, request.HeadBytesLength + request.BodyLength);
|
||||
return new AdapterResult() { FilterResult = FilterResult.Success, Content = bytes };
|
||||
}
|
||||
else
|
||||
{
|
||||
return new AdapterResult() { FilterResult = FilterResult.Success };
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://kimdiego2098.github.io/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace ThingsGateway.Foundation.Modbus;
|
||||
|
||||
/// <inheritdoc/>
|
||||
internal class ModbusRtuServerDataHandleAdapter : ReadWriteDevicesSingleStreamDataHandleAdapter<ModbusRtuServerMessage>
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
protected override AdapterResult UnpackResponse(ModbusRtuServerMessage request, IByteBlock byteBlock)
|
||||
{
|
||||
var pos = byteBlock.Position;
|
||||
var result = ModbusHelper.CheckCrc(byteBlock);
|
||||
request.OperCode = result.OperCode;
|
||||
request.ErrorMessage = result.ErrorMessage;
|
||||
if (result.IsSuccess)
|
||||
{
|
||||
byteBlock.Position = pos;
|
||||
var bytes = ModbusHelper.ModbusServerAnalysisAddressValue(request, byteBlock);
|
||||
request.Bytes = byteBlock.AsSegment(pos, request.HeadBytesLength + request.BodyLength);
|
||||
return new AdapterResult() { FilterResult = FilterResult.Success, Content = bytes };
|
||||
}
|
||||
else
|
||||
{
|
||||
return new AdapterResult() { FilterResult = FilterResult.Success };
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://kimdiego2098.github.io/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace ThingsGateway.Foundation.Modbus;
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc/>
|
||||
/// </summary>
|
||||
internal class ModbusRtuServerMessage : MessageBase, IResultMessage, IModbusServerMessage
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public ModbusAddress ModbusAddress { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 当前关联的字节数组
|
||||
/// </summary>
|
||||
public ReadOnlyMemory<byte> Bytes { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public int Length { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override int HeadBytesLength => 7; //主站发送的报文最低也有8个字节
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool CheckHeadBytes(byte[]? headBytes)
|
||||
{
|
||||
if (headBytes == null || headBytes.Length <= 0) return false;
|
||||
//01 03 00 00 00 01 xx xx
|
||||
//01 04 00 00 00 01 xx xx
|
||||
//01 01 00 00 00 01 xx xx
|
||||
//01 02 00 00 00 01 xx xx
|
||||
//01 05 00 00 00 00 xx xx
|
||||
//01 06 00 00 00 00 xx xx
|
||||
//01 0f 00 00 00 01 01 00 xx xx
|
||||
//01 10 00 00 00 01 02 00 00 xx xx
|
||||
|
||||
//modbusRtu 读取/单写
|
||||
if (headBytes[1] <= 0x06)
|
||||
{
|
||||
BodyLength = 1; //crc l
|
||||
return true;
|
||||
}
|
||||
else if (headBytes[1] <= 0x10)
|
||||
{
|
||||
//modbusRtu 多写
|
||||
BodyLength = headBytes[6] + 2; //数据区+crc
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://kimdiego2098.github.io/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace ThingsGateway.Foundation.Modbus;
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc/>
|
||||
/// </summary>
|
||||
internal class ModbusRtuSlaveMessage : MessageBase, IResultMessage
|
||||
{
|
||||
public ModbusRequest Request { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// 当前关联的字节数组
|
||||
/// </summary>
|
||||
public ReadOnlyMemory<byte> Bytes { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override int HeaderLength => 7; //主站发送的报文最低也有8个字节
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool CheckHead<TByteBlock>(ref TByteBlock byteBlock)
|
||||
{
|
||||
Request.Station = byteBlock.ReadByte();
|
||||
Request.FunctionCode = byteBlock.ReadByte();
|
||||
Request.StartAddress = byteBlock.ReadUInt16(EndianType.Big);
|
||||
if (Request.FunctionCode == 3 || Request.FunctionCode == 4)
|
||||
{
|
||||
Request.Length = (ushort)(byteBlock.ReadUInt16(EndianType.Big) * 2);
|
||||
BodyLength = 1;
|
||||
return true;
|
||||
}
|
||||
else if (Request.FunctionCode == 1 || Request.FunctionCode == 2)
|
||||
{
|
||||
Request.Length = (ushort)Math.Ceiling(byteBlock.ReadUInt16(EndianType.Big) / 8.0);
|
||||
BodyLength = 1;
|
||||
return true;
|
||||
}
|
||||
else if (Request.FunctionCode == 5)
|
||||
{
|
||||
Request.Data = byteBlock.AsSegmentTake(2);
|
||||
BodyLength = 1;
|
||||
return true;
|
||||
}
|
||||
else if (Request.FunctionCode == 6)
|
||||
{
|
||||
Request.Data = byteBlock.AsSegmentTake(2);
|
||||
BodyLength = 1;
|
||||
return true;
|
||||
}
|
||||
else if (Request.FunctionCode == 15)
|
||||
{
|
||||
Request.Length = (ushort)Math.Ceiling(byteBlock.ReadUInt16(EndianType.Big) / 8.0);
|
||||
BodyLength = Request.Length + 2;
|
||||
return true;
|
||||
}
|
||||
else if (Request.FunctionCode == 16)
|
||||
{
|
||||
Request.Length = (ushort)(byteBlock.ReadUInt16(EndianType.Big) * 2);
|
||||
BodyLength = Request.Length + 2;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public override FilterResult CheckBody<TByteBlock>(ref TByteBlock byteBlock)
|
||||
{
|
||||
var pos = byteBlock.Position - HeaderLength;
|
||||
var crcLen = 0;
|
||||
this.Bytes = byteBlock.AsSegment(pos, HeaderLength + BodyLength);
|
||||
|
||||
if (Request.FunctionCode == 15)
|
||||
{
|
||||
Request.Data = byteBlock.AsSegmentTake(Request.Length);
|
||||
}
|
||||
else if (Request.FunctionCode == 16)
|
||||
{
|
||||
Request.Data = byteBlock.AsSegmentTake(Request.Length);
|
||||
}
|
||||
|
||||
crcLen = HeaderLength + BodyLength - 2;
|
||||
|
||||
var crc = CRC16Utils.Crc16Only(byteBlock.Span.Slice(pos, crcLen));
|
||||
|
||||
//Crc
|
||||
var checkCrc = byteBlock.Span.Slice(pos + crcLen, 2).ToArray();
|
||||
if (crc.SequenceEqual(checkCrc))
|
||||
{
|
||||
return FilterResult.Success;
|
||||
}
|
||||
|
||||
return FilterResult.GoOn;
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://kimdiego2098.github.io/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace ThingsGateway.Foundation.Modbus;
|
||||
|
||||
/// <inheritdoc/>
|
||||
internal class ModbusTcpServerDataHandleAdapter : ReadWriteDevicesSingleStreamDataHandleAdapter<ModbusTcpServerMessage>
|
||||
{
|
||||
public ModbusTcpServerDataHandleAdapter()
|
||||
{
|
||||
this.MaxPackageSize = 1024 * 1024 * 1024;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override AdapterResult UnpackResponse(ModbusTcpServerMessage request, IByteBlock byteBlock)
|
||||
{
|
||||
var pos = byteBlock.Position;
|
||||
byteBlock.Position = byteBlock.Position + 6;
|
||||
var bytes = ModbusHelper.ModbusServerAnalysisAddressValue(request, byteBlock);
|
||||
request.Bytes = byteBlock.AsSegment(pos, request.HeadBytesLength + request.BodyLength);
|
||||
return new AdapterResult() { FilterResult = FilterResult.Success, Content = bytes };
|
||||
}
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://kimdiego2098.github.io/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace ThingsGateway.Foundation.Modbus;
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc/>
|
||||
/// </summary>
|
||||
internal class ModbusTcpServerMessage : MessageBase, IResultMessage, IModbusServerMessage
|
||||
{
|
||||
/// <summary>
|
||||
/// 当前关联的地址
|
||||
/// </summary>
|
||||
public ModbusAddress ModbusAddress { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 当前关联的字节数组
|
||||
/// </summary>
|
||||
public ReadOnlyMemory<byte> Bytes { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 当前读写的数据长度
|
||||
/// </summary>
|
||||
public int Length { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override int HeadBytesLength => 6;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool CheckHeadBytes(byte[]? headBytes)
|
||||
{
|
||||
if (headBytes == null || headBytes.Length <= 0) return false;
|
||||
|
||||
int num = (headBytes[4] * 256) + headBytes[5];
|
||||
if (num > 0xff + 3) return false;
|
||||
BodyLength = num;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://kimdiego2098.github.io/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace ThingsGateway.Foundation.Modbus;
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc/>
|
||||
/// </summary>
|
||||
internal class ModbusTcpSlaveMessage : MessageBase, IResultMessage
|
||||
{
|
||||
public ModbusRequest Request { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// 当前关联的字节数组
|
||||
/// </summary>
|
||||
public ReadOnlyMemory<byte> Bytes { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override int HeaderLength => 12;
|
||||
|
||||
public override bool CheckHead<TByteBlock>(ref TByteBlock byteBlock)
|
||||
{
|
||||
this.Sign = byteBlock.ReadUInt16(EndianType.Big);
|
||||
byteBlock.Position += 2;
|
||||
this.BodyLength = byteBlock.ReadUInt16(EndianType.Big) - 6;
|
||||
Request.Station = byteBlock.ReadByte();
|
||||
Request.FunctionCode = byteBlock.ReadByte();
|
||||
|
||||
Request.StartAddress = byteBlock.ReadUInt16(EndianType.Big);
|
||||
|
||||
if (Request.FunctionCode == 3 || Request.FunctionCode == 4)
|
||||
{
|
||||
Request.Length = (ushort)(byteBlock.ReadUInt16(EndianType.Big) * 2);
|
||||
return true;
|
||||
}
|
||||
else if (Request.FunctionCode == 1 || Request.FunctionCode == 2)
|
||||
{
|
||||
Request.Length = (ushort)Math.Ceiling(byteBlock.ReadUInt16(EndianType.Big) / 8.0);
|
||||
return true;
|
||||
}
|
||||
else if (Request.FunctionCode == 5 || Request.FunctionCode == 6)
|
||||
{
|
||||
Request.Data = byteBlock.AsSegmentTake(2);
|
||||
return true;
|
||||
}
|
||||
else if (Request.FunctionCode == 15)
|
||||
{
|
||||
Request.Length = (ushort)Math.Ceiling(byteBlock.ReadUInt16(EndianType.Big) / 8.0);
|
||||
return true;
|
||||
}
|
||||
else if (Request.FunctionCode == 16)
|
||||
{
|
||||
Request.Length = (ushort)(byteBlock.ReadUInt16(EndianType.Big) * 2);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public override FilterResult CheckBody<TByteBlock>(ref TByteBlock byteBlock)
|
||||
{
|
||||
var pos = byteBlock.Position - HeaderLength;
|
||||
this.Bytes = byteBlock.AsSegment(pos, HeaderLength + BodyLength);
|
||||
|
||||
if (Request.FunctionCode == 15 || Request.FunctionCode == 16)
|
||||
{
|
||||
byteBlock.Position += 1;
|
||||
Request.Data = byteBlock.AsSegmentTake(Request.Length);
|
||||
}
|
||||
return FilterResult.Success;
|
||||
}
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://kimdiego2098.github.io/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace ThingsGateway.Foundation.Modbus;
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc/>
|
||||
/// </summary>
|
||||
internal class ModbusUdpServerDataHandleAdapter : ReadWriteDevicesUdpDataHandleAdapter<ModbusTcpServerMessage>
|
||||
{
|
||||
public ModbusUdpServerDataHandleAdapter()
|
||||
{
|
||||
this.MaxPackageSize = 1024 * 1024 * 1024;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override AdapterResult UnpackResponse(ModbusTcpServerMessage request, IByteBlock byteBlock)
|
||||
{
|
||||
var pos = byteBlock.Position;
|
||||
byteBlock.Position = byteBlock.Position + 6;
|
||||
var bytes = ModbusHelper.ModbusServerAnalysisAddressValue(request, byteBlock);
|
||||
request.Bytes = byteBlock.AsSegment(pos, request.HeadBytesLength + request.BodyLength);
|
||||
return new AdapterResult() { FilterResult = FilterResult.Success, Content = bytes };
|
||||
}
|
||||
}
|
||||
@@ -10,8 +10,6 @@
|
||||
|
||||
using System.Collections.Concurrent;
|
||||
|
||||
using ThingsGateway.Foundation.Extension.Generic;
|
||||
|
||||
using TouchSocket.Sockets;
|
||||
|
||||
namespace ThingsGateway.Foundation.Modbus;
|
||||
@@ -19,7 +17,7 @@ namespace ThingsGateway.Foundation.Modbus;
|
||||
/// <summary>
|
||||
/// ChannelEventHandler
|
||||
/// </summary>
|
||||
public delegate ValueTask<OperResult> ModbusServerWriteEventHandler(ModbusAddress modbusAddress, byte[] writeValue, IThingsGatewayBitConverter bitConverter, IClientChannel channel);
|
||||
public delegate ValueTask<OperResult> ModbusServerWriteEventHandler(ModbusAddress modbusAddress, IThingsGatewayBitConverter bitConverter, IClientChannel channel);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public class ModbusSlave : ProtocolBase, ITcpService, IDtuClient
|
||||
@@ -71,7 +69,7 @@ public class ModbusSlave : ProtocolBase, ITcpService, IDtuClient
|
||||
/// </summary>
|
||||
public int MaxClientCount { get; set; } = 10000;
|
||||
|
||||
public string DtuId { get; set; } = "TEST";
|
||||
public string DtuId { get; set; } = "DtuId";
|
||||
public int HeartbeatTime { get; set; } = 5;
|
||||
public string HeartbeatHexString { get; set; } = "FFFF8080";
|
||||
|
||||
@@ -134,13 +132,13 @@ public class ModbusSlave : ProtocolBase, ITcpService, IDtuClient
|
||||
case ChannelTypeEnum.TcpClient:
|
||||
case ChannelTypeEnum.TcpService:
|
||||
case ChannelTypeEnum.SerialPort:
|
||||
return new ModbusTcpServerDataHandleAdapter()
|
||||
return new ProtocolSingleStreamDataHandleAdapter<ModbusTcpSlaveMessage>()
|
||||
{
|
||||
CacheTimeout = TimeSpan.FromMilliseconds(CacheTimeout)
|
||||
};
|
||||
|
||||
case ChannelTypeEnum.UdpSession:
|
||||
return new ModbusUdpServerDataHandleAdapter()
|
||||
return new ProtocolUdpDataHandleAdapter<ModbusTcpSlaveMessage>()
|
||||
{
|
||||
};
|
||||
}
|
||||
@@ -152,19 +150,19 @@ public class ModbusSlave : ProtocolBase, ITcpService, IDtuClient
|
||||
case ChannelTypeEnum.TcpClient:
|
||||
case ChannelTypeEnum.TcpService:
|
||||
case ChannelTypeEnum.SerialPort:
|
||||
return new ModbusRtuServerDataHandleAdapter()
|
||||
return new ProtocolSingleStreamDataHandleAdapter<ModbusRtuSlaveMessage>()
|
||||
{
|
||||
CacheTimeout = TimeSpan.FromMilliseconds(CacheTimeout)
|
||||
};
|
||||
|
||||
case ChannelTypeEnum.UdpSession:
|
||||
return new ModbusRtuOverUdpServerDataHandleAdapter()
|
||||
return new ProtocolUdpDataHandleAdapter<ModbusRtuSlaveMessage>()
|
||||
{
|
||||
};
|
||||
}
|
||||
break;
|
||||
}
|
||||
return new ModbusTcpDataHandleAdapter()
|
||||
return new ProtocolSingleStreamDataHandleAdapter<ModbusTcpSlaveMessage>()
|
||||
{
|
||||
CacheTimeout = TimeSpan.FromMilliseconds(CacheTimeout)
|
||||
};
|
||||
@@ -177,7 +175,7 @@ public class ModbusSlave : ProtocolBase, ITcpService, IDtuClient
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
private void Init(ModbusAddress mAddress)
|
||||
private void Init(ModbusRequest mAddress)
|
||||
{
|
||||
ModbusServer01ByteBlocks.GetOrAdd(mAddress.Station, a => new ValueByteBlock(new byte[ushort.MaxValue * 2]));
|
||||
ModbusServer02ByteBlocks.GetOrAdd(mAddress.Station, a => new ValueByteBlock(new byte[ushort.MaxValue * 2]));
|
||||
@@ -219,194 +217,197 @@ public class ModbusSlave : ProtocolBase, ITcpService, IDtuClient
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
var requestInfo = e.RequestInfo;
|
||||
bool modbusRtu = false;
|
||||
ModbusRequest modbusRequest = default;
|
||||
ReadOnlyMemory<byte> Bytes = default;
|
||||
//接收外部报文
|
||||
if (requestInfo is IModbusServerMessage modbusServerMessage)
|
||||
if (requestInfo is ModbusRtuSlaveMessage modbusRtuSlaveMessage)
|
||||
{
|
||||
if (modbusServerMessage.ModbusAddress == null)
|
||||
if (!modbusRtuSlaveMessage.IsSuccess)
|
||||
{
|
||||
return;//无法解析直接返回
|
||||
return;
|
||||
}
|
||||
if (!modbusServerMessage.IsSuccess)
|
||||
modbusRequest = modbusRtuSlaveMessage.Request;
|
||||
Bytes = modbusRtuSlaveMessage.Bytes;
|
||||
modbusRtu = true;
|
||||
}
|
||||
else if (requestInfo is ModbusTcpSlaveMessage modbusTcpSlaveMessage)
|
||||
{
|
||||
if (!modbusTcpSlaveMessage.IsSuccess)
|
||||
{
|
||||
return;//无法解析直接返回
|
||||
return;
|
||||
}
|
||||
modbusRequest = modbusTcpSlaveMessage.Request;
|
||||
Bytes = modbusTcpSlaveMessage.Bytes;
|
||||
modbusRtu = false;
|
||||
}
|
||||
|
||||
if (modbusServerMessage.ModbusAddress.WriteFunction == null)//读取
|
||||
if (modbusRequest.FunctionCode <= 4)
|
||||
{
|
||||
var data = this.ModbusRequest(modbusRequest, true);
|
||||
if (data.IsSuccess)
|
||||
{
|
||||
var data = this.Read(modbusServerMessage.ModbusAddress.ToString(), modbusServerMessage.Length);
|
||||
if (data.IsSuccess)
|
||||
ValueByteBlock valueByteBlock = new(1024);
|
||||
try
|
||||
{
|
||||
ValueByteBlock valueByteBlock = new(1024);
|
||||
try
|
||||
if (modbusRtu)
|
||||
{
|
||||
//rtu返回头
|
||||
if (ModbusType == ModbusTypeEnum.ModbusRtu)
|
||||
valueByteBlock.Write(Bytes.Slice(0, 2).Span);
|
||||
if (modbusRequest.FunctionCode == 1 || modbusRequest.FunctionCode == 2)
|
||||
{
|
||||
valueByteBlock.Write(modbusServerMessage.Bytes.Slice(0, 2).Span);
|
||||
if (modbusServerMessage.ModbusAddress.ReadFunction == 1 || modbusServerMessage.ModbusAddress.ReadFunction == 2)
|
||||
{
|
||||
valueByteBlock.WriteByte((byte)(Math.Ceiling(modbusServerMessage.Length / 8.0)));
|
||||
valueByteBlock.Write(data.Content.ToArray().Select(m => m > 0).ToArray().BoolArrayToByte().SelectMiddle(0, (int)Math.Ceiling(modbusServerMessage.Length / 8.0)));
|
||||
}
|
||||
else
|
||||
{
|
||||
valueByteBlock.WriteByte((byte)data.Content.Length);
|
||||
valueByteBlock.Write(data.Content.Span);
|
||||
}
|
||||
valueByteBlock.Write(CRC16Utils.Crc16Only(valueByteBlock.Memory));
|
||||
await ReturnData(client, e, valueByteBlock.Memory);
|
||||
valueByteBlock.WriteByte((byte)modbusRequest.Length);
|
||||
valueByteBlock.Write(data.Content.Span);
|
||||
}
|
||||
else
|
||||
{
|
||||
valueByteBlock.Write(modbusServerMessage.Bytes.Slice(0, 8).Span);
|
||||
if (modbusServerMessage.ModbusAddress.ReadFunction == 1 || modbusServerMessage.ModbusAddress.ReadFunction == 2)
|
||||
{
|
||||
valueByteBlock.WriteByte((byte)(Math.Ceiling(modbusServerMessage.Length / 8.0)));
|
||||
valueByteBlock.Write(data.Content.ToArray().Select(m => m > 0).ToArray().BoolArrayToByte().SelectMiddle(0, (int)Math.Ceiling(modbusServerMessage.Length / 8.0)));
|
||||
}
|
||||
else
|
||||
{
|
||||
valueByteBlock.WriteByte((byte)data.Content.Length);
|
||||
valueByteBlock.Write(data.Content.Span);
|
||||
}
|
||||
valueByteBlock[5] = (byte)(valueByteBlock.Length - 6);
|
||||
await ReturnData(client, e, valueByteBlock.Memory);
|
||||
valueByteBlock.WriteByte((byte)data.Content.Length);
|
||||
valueByteBlock.Write(data.Content.Span);
|
||||
}
|
||||
valueByteBlock.Write(CRC16Utils.Crc16Only(valueByteBlock.Memory.Span));
|
||||
await ReturnData(client, valueByteBlock.Memory, e);
|
||||
}
|
||||
finally
|
||||
else
|
||||
{
|
||||
valueByteBlock.SafeDispose();
|
||||
valueByteBlock.Write(Bytes.Slice(0, 8).Span);
|
||||
if (modbusRequest.FunctionCode == 1 || modbusRequest.FunctionCode == 2)
|
||||
{
|
||||
valueByteBlock.WriteByte((byte)modbusRequest.Length);
|
||||
valueByteBlock.Write(data.Content.Span);
|
||||
}
|
||||
else
|
||||
{
|
||||
valueByteBlock.WriteByte((byte)data.Content.Length);
|
||||
valueByteBlock.Write(data.Content.Span);
|
||||
}
|
||||
valueByteBlock[5] = (byte)(valueByteBlock.Length - 6);
|
||||
await ReturnData(client, valueByteBlock.Memory, e);
|
||||
}
|
||||
}
|
||||
else
|
||||
finally
|
||||
{
|
||||
await WriteError(this.ModbusType, client, modbusServerMessage, e);//返回错误码
|
||||
valueByteBlock.SafeDispose();
|
||||
}
|
||||
}
|
||||
else//写入
|
||||
else
|
||||
{
|
||||
var coreData = modbusServerMessage.Content;
|
||||
if (modbusServerMessage.ModbusAddress.ReadFunction == 1 || modbusServerMessage.ModbusAddress.ReadFunction == 2)
|
||||
await WriteError(modbusRtu, client, Bytes, e);//返回错误码
|
||||
}
|
||||
}
|
||||
else//写入
|
||||
{
|
||||
if (modbusRequest.FunctionCode == 5)
|
||||
{
|
||||
//写入继电器
|
||||
if (this.WriteData != null)
|
||||
{
|
||||
//写入继电器
|
||||
if (this.WriteData != null)
|
||||
var modbusAddress = new ModbusAddress(modbusRequest) { WriteFunctionCode = modbusRequest.FunctionCode, FunctionCode = 1 };
|
||||
// 接收外部写入时,传出变量地址/写入字节组/转换规则/客户端
|
||||
if ((await this.WriteData(modbusAddress, this.ThingsGatewayBitConverter, client).ConfigureAwait(false)).IsSuccess)
|
||||
{
|
||||
// 接收外部写入时,传出变量地址/写入字节组/转换规则/客户端
|
||||
if ((await this.WriteData(modbusServerMessage.ModbusAddress, modbusServerMessage.Content, this.ThingsGatewayBitConverter, client).ConfigureAwait(false)).IsSuccess)
|
||||
await WriteSuccess(modbusRtu, client, Bytes, e);
|
||||
if (this.IsWriteMemory)
|
||||
{
|
||||
await WriteSuccess(this.ModbusType, client, modbusServerMessage, e);
|
||||
if (this.IsWriteMemory)
|
||||
{
|
||||
var result = this.Write(modbusServerMessage.ModbusAddress.ToString(), coreData, modbusServerMessage.Length);
|
||||
if (result.IsSuccess)
|
||||
await WriteSuccess(this.ModbusType, client, modbusServerMessage, e);
|
||||
else
|
||||
await WriteError(this.ModbusType, client, modbusServerMessage, e);
|
||||
}
|
||||
var result = this.ModbusRequest(modbusRequest, false);
|
||||
if (result.IsSuccess)
|
||||
await WriteSuccess(modbusRtu, client, Bytes, e);
|
||||
else
|
||||
await WriteSuccess(this.ModbusType, client, modbusServerMessage, e);
|
||||
await WriteError(modbusRtu, client, Bytes, e);
|
||||
}
|
||||
else
|
||||
{
|
||||
await WriteError(this.ModbusType, client, modbusServerMessage, e);
|
||||
}
|
||||
await WriteSuccess(modbusRtu, client, Bytes, e);
|
||||
}
|
||||
else
|
||||
{
|
||||
//写入内存区
|
||||
var result = this.Write(modbusServerMessage.ModbusAddress.ToString(), coreData, modbusServerMessage.Length);
|
||||
if (result.IsSuccess)
|
||||
{
|
||||
await WriteSuccess(this.ModbusType, client, modbusServerMessage, e);
|
||||
}
|
||||
else
|
||||
{
|
||||
await WriteError(this.ModbusType, client, modbusServerMessage, e);
|
||||
}
|
||||
await WriteError(modbusRtu, client, Bytes, e);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
//写入寄存器
|
||||
if (this.WriteData != null)
|
||||
//写入内存区
|
||||
var result = this.ModbusRequest(modbusRequest, false);
|
||||
if (result.IsSuccess)
|
||||
{
|
||||
if ((await this.WriteData(modbusServerMessage.ModbusAddress, modbusServerMessage.Content, this.ThingsGatewayBitConverter, client).ConfigureAwait(false)).IsSuccess)
|
||||
{
|
||||
if (this.IsWriteMemory)
|
||||
{
|
||||
var result = this.Write(modbusServerMessage.ModbusAddress.ToString(), coreData);
|
||||
if (result.IsSuccess)
|
||||
await WriteSuccess(this.ModbusType, client, modbusServerMessage, e);
|
||||
else
|
||||
await WriteError(this.ModbusType, client, modbusServerMessage, e);
|
||||
}
|
||||
else
|
||||
await WriteSuccess(this.ModbusType, client, modbusServerMessage, e);
|
||||
}
|
||||
else
|
||||
{
|
||||
await WriteError(this.ModbusType, client, modbusServerMessage, e);
|
||||
}
|
||||
await WriteSuccess(modbusRtu, client, Bytes, e);
|
||||
}
|
||||
else
|
||||
{
|
||||
var result = this.Write(modbusServerMessage.ModbusAddress.ToString(), coreData);
|
||||
if (result.IsSuccess)
|
||||
await WriteError(modbusRtu, client, Bytes, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
//写入寄存器
|
||||
if (this.WriteData != null)
|
||||
{
|
||||
var modbusAddress = new ModbusAddress(modbusRequest) { WriteFunctionCode = modbusRequest.FunctionCode, FunctionCode = 3 };
|
||||
if ((await this.WriteData(modbusAddress, this.ThingsGatewayBitConverter, client).ConfigureAwait(false)).IsSuccess)
|
||||
{
|
||||
if (this.IsWriteMemory)
|
||||
{
|
||||
await WriteSuccess(this.ModbusType, client, modbusServerMessage, e);
|
||||
var result = this.ModbusRequest(modbusRequest, false);
|
||||
if (result.IsSuccess)
|
||||
await WriteSuccess(modbusRtu, client, Bytes, e);
|
||||
else
|
||||
await WriteError(modbusRtu, client, Bytes, e);
|
||||
}
|
||||
else
|
||||
{
|
||||
await WriteError(this.ModbusType, client, modbusServerMessage, e);
|
||||
}
|
||||
await WriteSuccess(modbusRtu, client, Bytes, e);
|
||||
}
|
||||
else
|
||||
{
|
||||
await WriteError(modbusRtu, client, Bytes, e);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var result = this.ModbusRequest(modbusRequest, false);
|
||||
if (result.IsSuccess)
|
||||
{
|
||||
await WriteSuccess(modbusRtu, client, Bytes, e);
|
||||
}
|
||||
else
|
||||
{
|
||||
await WriteError(modbusRtu, client, Bytes, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
return EasyTask.CompletedTask;
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private async Task ReturnData(IClientChannel client, ReceivedDataEventArgs e, ReadOnlyMemory<byte> sendData)
|
||||
private async Task ReturnData(IClientChannel client, ReadOnlyMemory<byte> sendData, ReceivedDataEventArgs e)
|
||||
{
|
||||
if (SendDelayTime > 0)
|
||||
await Task.Delay(SendDelayTime).ConfigureAwait(false);
|
||||
if (client is IUdpClientSender udpClientSender)
|
||||
if (ModbusType == ModbusTypeEnum.ModbusRtu)
|
||||
await udpClientSender.SendAsync(((UdpReceivedDataEventArgs)e).EndPoint, new SendMessage(sendData));
|
||||
else
|
||||
await udpClientSender.SendAsync(((UdpReceivedDataEventArgs)e).EndPoint, sendData);
|
||||
else
|
||||
if (ModbusType == ModbusTypeEnum.ModbusRtu)
|
||||
await client.SendAsync(new SendMessage(sendData));
|
||||
await udpClientSender.SendAsync(((UdpReceivedDataEventArgs)e).EndPoint, sendData);
|
||||
else
|
||||
await client.SendAsync(sendData);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 返回错误码
|
||||
/// </summary>
|
||||
private async Task WriteError(ModbusTypeEnum modbusType, IClientChannel client, IModbusServerMessage modbusServerMessage, ReceivedDataEventArgs e)
|
||||
private async Task WriteError(bool modbusRtu, IClientChannel client, ReadOnlyMemory<byte> bytes, ReceivedDataEventArgs e)
|
||||
{
|
||||
ValueByteBlock valueByteBlock = new(20);
|
||||
try
|
||||
{
|
||||
if (modbusType == ModbusTypeEnum.ModbusRtu)
|
||||
if (modbusRtu)
|
||||
{
|
||||
valueByteBlock.Write(modbusServerMessage.Bytes.Slice(0, 2).Span);
|
||||
valueByteBlock.Write(bytes.Slice(0, 2).Span);
|
||||
valueByteBlock.WriteByte((byte)1);
|
||||
valueByteBlock.Write(CRC16Utils.Crc16Only(valueByteBlock.Memory));
|
||||
valueByteBlock.Write(CRC16Utils.Crc16Only(valueByteBlock.Span));
|
||||
valueByteBlock[1] = (byte)(valueByteBlock[1] + 128);
|
||||
await ReturnData(client, e, valueByteBlock.Memory);
|
||||
}
|
||||
else
|
||||
{
|
||||
valueByteBlock.Write(modbusServerMessage.Bytes.Slice(0, 8).Span);
|
||||
valueByteBlock.Write(bytes.Slice(0, 8).Span);
|
||||
valueByteBlock.WriteByte((byte)1);
|
||||
valueByteBlock[5] = (byte)(valueByteBlock.Length - 6);
|
||||
valueByteBlock[7] = (byte)(valueByteBlock[7] + 128);
|
||||
await ReturnData(client, e, valueByteBlock.Memory);
|
||||
}
|
||||
await ReturnData(client, valueByteBlock.Memory, e);
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -414,35 +415,36 @@ public class ModbusSlave : ProtocolBase, ITcpService, IDtuClient
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 返回成功
|
||||
/// </summary>
|
||||
internal async Task WriteSuccess(ModbusTypeEnum modbusType, IClientChannel client, IModbusServerMessage modbusServerMessage, ReceivedDataEventArgs e)
|
||||
private async Task WriteSuccess(bool modbusRtu, IClientChannel client, ReadOnlyMemory<byte> bytes, ReceivedDataEventArgs e)
|
||||
{
|
||||
ValueByteBlock valueByteBlock = new(20);
|
||||
if (modbusType == ModbusTypeEnum.ModbusRtu)
|
||||
try
|
||||
{
|
||||
valueByteBlock.Write(modbusServerMessage.Bytes.Slice(0, 6).Span);
|
||||
valueByteBlock.Write(CRC16Utils.Crc16Only(valueByteBlock.Memory));
|
||||
await ReturnData(client, e, valueByteBlock.Memory);
|
||||
if (modbusRtu)
|
||||
{
|
||||
valueByteBlock.Write(bytes.Slice(0, 6).Span);
|
||||
valueByteBlock.Write(CRC16Utils.Crc16Only(valueByteBlock.Span));
|
||||
}
|
||||
else
|
||||
{
|
||||
valueByteBlock.Write(bytes.Slice(0, 12).Span);
|
||||
valueByteBlock[5] = (byte)(valueByteBlock.Length - 6);
|
||||
}
|
||||
await ReturnData(client, valueByteBlock.Memory, e);
|
||||
}
|
||||
else
|
||||
finally
|
||||
{
|
||||
valueByteBlock.Write(modbusServerMessage.Bytes.Slice(0, 12).Span);
|
||||
valueByteBlock[5] = (byte)(valueByteBlock.Length - 6);
|
||||
await ReturnData(client, e, valueByteBlock.Memory);
|
||||
valueByteBlock.SafeDispose();
|
||||
}
|
||||
}
|
||||
|
||||
private readonly ReaderWriterLockSlim _lockSlim = new();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public OperResult<Memory<byte>> Read(string address, int length, CancellationToken cancellationToken = default)
|
||||
public OperResult<ReadOnlyMemory<byte>> ModbusRequest(ModbusRequest mAddress, bool read, CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
ModbusAddress mAddress = ModbusAddress.ParseFrom(address, this.Station);
|
||||
|
||||
if (this.MulStation)
|
||||
{
|
||||
this.Init(mAddress);
|
||||
@@ -455,29 +457,67 @@ public class ModbusSlave : ProtocolBase, ITcpService, IDtuClient
|
||||
}
|
||||
this.Init(mAddress);
|
||||
}
|
||||
|
||||
using (new ReadLock(this._lockSlim))
|
||||
var ModbusServer01ByteBlock = this.ModbusServer01ByteBlocks[mAddress.Station];
|
||||
var ModbusServer02ByteBlock = this.ModbusServer02ByteBlocks[mAddress.Station];
|
||||
var ModbusServer03ByteBlock = this.ModbusServer03ByteBlocks[mAddress.Station];
|
||||
var ModbusServer04ByteBlock = this.ModbusServer04ByteBlocks[mAddress.Station];
|
||||
if (read)
|
||||
{
|
||||
var ModbusServer01ByteBlock = this.ModbusServer01ByteBlocks[mAddress.Station];
|
||||
var ModbusServer02ByteBlock = this.ModbusServer02ByteBlocks[mAddress.Station];
|
||||
var ModbusServer03ByteBlock = this.ModbusServer03ByteBlocks[mAddress.Station];
|
||||
var ModbusServer04ByteBlock = this.ModbusServer04ByteBlocks[mAddress.Station];
|
||||
int len = mAddress.ReadFunction == 2 || mAddress.ReadFunction == 1 ? length : length * this.RegisterByteLength;
|
||||
|
||||
switch (mAddress.ReadFunction)
|
||||
using (new ReadLock(this._lockSlim))
|
||||
{
|
||||
case 1:
|
||||
return OperResult.CreateSuccessResult(ModbusServer01ByteBlock.ToMemory(mAddress.AddressStart, len));
|
||||
int len = mAddress.Length;
|
||||
|
||||
case 2:
|
||||
return OperResult.CreateSuccessResult(ModbusServer02ByteBlock.ToMemory(mAddress.AddressStart, len));
|
||||
switch (mAddress.FunctionCode)
|
||||
{
|
||||
case 1:
|
||||
return OperResult.CreateSuccessResult(ModbusServer01ByteBlock.Memory.Slice(mAddress.StartAddress, len));
|
||||
|
||||
case 3:
|
||||
case 2:
|
||||
return OperResult.CreateSuccessResult(ModbusServer02ByteBlock.Memory.Slice(mAddress.StartAddress, len));
|
||||
|
||||
return OperResult.CreateSuccessResult(ModbusServer03ByteBlock.ToMemory(mAddress.AddressStart * this.RegisterByteLength, len));
|
||||
case 3:
|
||||
|
||||
case 4:
|
||||
return OperResult.CreateSuccessResult(ModbusServer04ByteBlock.ToMemory(mAddress.AddressStart * this.RegisterByteLength, len));
|
||||
return OperResult.CreateSuccessResult(ModbusServer03ByteBlock.Memory.Slice(mAddress.StartAddress * this.RegisterByteLength, len));
|
||||
|
||||
case 4:
|
||||
return OperResult.CreateSuccessResult(ModbusServer04ByteBlock.Memory.Slice(mAddress.StartAddress * this.RegisterByteLength, len));
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (mAddress.FunctionCode == 0x05)
|
||||
{
|
||||
var data = ModbusServer01ByteBlock.Memory.Slice(mAddress.StartAddress, 1);
|
||||
mAddress.Data = new ReadOnlyMemory<byte>([data.Span[0].SetBit(mAddress.StartAddress % 8, mAddress.Data.Span[0] == 0xff)]);
|
||||
}
|
||||
using (new WriteLock(this._lockSlim))
|
||||
{
|
||||
switch (mAddress.FunctionCode)
|
||||
{
|
||||
case 2:
|
||||
ModbusServer02ByteBlock.Position = mAddress.StartAddress;
|
||||
ModbusServer02ByteBlock.Write(mAddress.Data.Span);
|
||||
return new();
|
||||
|
||||
case 1:
|
||||
case 5:
|
||||
case 15:
|
||||
ModbusServer01ByteBlock.Position = mAddress.StartAddress;
|
||||
ModbusServer01ByteBlock.Write(mAddress.Data.Span);
|
||||
return new();
|
||||
|
||||
case 4:
|
||||
ModbusServer04ByteBlock.Position = mAddress.StartAddress;
|
||||
ModbusServer04ByteBlock.Write(mAddress.Data.Span);
|
||||
return new();
|
||||
|
||||
case 6:
|
||||
case 16:
|
||||
ModbusServer03ByteBlock.Position = mAddress.StartAddress * this.RegisterByteLength;
|
||||
ModbusServer03ByteBlock.Write(mAddress.Data.Span);
|
||||
return new();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -492,7 +532,9 @@ public class ModbusSlave : ProtocolBase, ITcpService, IDtuClient
|
||||
/// <inheritdoc/>
|
||||
public override ValueTask<OperResult<byte[]>> ReadAsync(string address, int length, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var result = Read(address, length, cancellationToken);
|
||||
ModbusAddress mAddress = ModbusAddress.ParseFrom(address, this.Station);
|
||||
mAddress.Length = (ushort)(length * RegisterByteLength);
|
||||
var result = ModbusRequest(mAddress, true, cancellationToken);
|
||||
if (result.IsSuccess)
|
||||
{
|
||||
return EasyValueTask.FromResult(new OperResult<byte[]>() { Content = result.Content.ToArray() });
|
||||
@@ -503,74 +545,36 @@ public class ModbusSlave : ProtocolBase, ITcpService, IDtuClient
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public OperResult Write(string address, byte[] value, int? length = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
length ??= value.Length;
|
||||
ModbusAddress mAddress = ModbusAddress.ParseFrom(address, this.Station);
|
||||
|
||||
if (this.MulStation)
|
||||
{
|
||||
this.Init(mAddress);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (this.Station != mAddress.Station)
|
||||
{
|
||||
return new OperResult<byte[]>(ModbusResource.Localizer["StationNotSame", mAddress.Station, this.Station]);
|
||||
}
|
||||
this.Init(mAddress);
|
||||
}
|
||||
|
||||
using (new WriteLock(this._lockSlim))
|
||||
{
|
||||
var ModbusServer01ByteBlock = this.ModbusServer01ByteBlocks[mAddress.Station];
|
||||
var ModbusServer02ByteBlock = this.ModbusServer02ByteBlocks[mAddress.Station];
|
||||
var ModbusServer03ByteBlock = this.ModbusServer03ByteBlocks[mAddress.Station];
|
||||
var ModbusServer04ByteBlock = this.ModbusServer04ByteBlocks[mAddress.Station];
|
||||
switch (mAddress.ReadFunction)
|
||||
{
|
||||
case 1:
|
||||
ModbusServer01ByteBlock.Position = mAddress.AddressStart;
|
||||
ModbusServer01ByteBlock.Write(value.ByteToBoolArray(length.Value).BoolArrayToByte());
|
||||
return OperResult.Success;
|
||||
|
||||
case 2:
|
||||
ModbusServer02ByteBlock.Position = mAddress.AddressStart;
|
||||
ModbusServer02ByteBlock.Write(value);
|
||||
return OperResult.Success;
|
||||
|
||||
case 3:
|
||||
ModbusServer03ByteBlock.Position = mAddress.AddressStart * this.RegisterByteLength;
|
||||
ModbusServer03ByteBlock.Write(value);
|
||||
return OperResult.Success;
|
||||
|
||||
case 4:
|
||||
ModbusServer04ByteBlock.Position = mAddress.AddressStart * this.RegisterByteLength;
|
||||
ModbusServer04ByteBlock.Write(value);
|
||||
return OperResult.Success;
|
||||
}
|
||||
}
|
||||
return new OperResult<byte[]>(ModbusResource.Localizer["FunctionError"]);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new OperResult(ex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override ValueTask<OperResult> WriteAsync(string address, byte[] value, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return EasyValueTask.FromResult(Write(address, value, cancellationToken: cancellationToken));
|
||||
ModbusAddress mAddress = ModbusAddress.ParseFrom(address, this.Station);
|
||||
mAddress.Data = value;
|
||||
var result = ModbusRequest(mAddress, false, cancellationToken);
|
||||
if (result.IsSuccess)
|
||||
{
|
||||
return EasyValueTask.FromResult(new OperResult());
|
||||
}
|
||||
else
|
||||
{
|
||||
return EasyValueTask.FromResult(new OperResult(result));
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override ValueTask<OperResult> WriteAsync(string address, bool[] value, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return EasyValueTask.FromResult(Write(address, value.BoolArrayToByte(), cancellationToken: cancellationToken));
|
||||
ModbusAddress mAddress = ModbusAddress.ParseFrom(address, this.Station);
|
||||
mAddress.Data = value.BoolArrayToByte();
|
||||
var result = ModbusRequest(mAddress, false, cancellationToken);
|
||||
if (result.IsSuccess)
|
||||
{
|
||||
return EasyValueTask.FromResult(new OperResult());
|
||||
}
|
||||
else
|
||||
{
|
||||
return EasyValueTask.FromResult(new OperResult(result));
|
||||
}
|
||||
}
|
||||
|
||||
#endregion 核心
|
||||
|
||||
@@ -89,7 +89,7 @@ internal class OpcDiscovery
|
||||
//2
|
||||
m_server.EnumClassesOfCategories(
|
||||
1,
|
||||
new Guid[] { catid },
|
||||
[catid],
|
||||
0,
|
||||
null,
|
||||
out enumerator);
|
||||
@@ -117,7 +117,7 @@ internal class OpcDiscovery
|
||||
IOPCEnumGUID enumerator = null;
|
||||
m_server.EnumClassesOfCategories(
|
||||
1,
|
||||
new Guid[] { catid },
|
||||
[catid],
|
||||
0,
|
||||
null,
|
||||
out enumerator);
|
||||
|
||||
@@ -19,7 +19,7 @@ public class BrowseElement : ICloneable
|
||||
private string m_itemName;
|
||||
private string m_itemPath;
|
||||
private string m_name;
|
||||
private ItemProperty[] m_properties = new ItemProperty[0];
|
||||
private ItemProperty[] m_properties = [];
|
||||
|
||||
public bool HasChildren
|
||||
{
|
||||
|
||||
@@ -512,7 +512,7 @@ public class FormUtils
|
||||
}
|
||||
|
||||
// construct the event based on the known event type.
|
||||
BaseEventState e = (BaseEventState)Activator.CreateInstance(knownType, new object[] { (NodeState)null });
|
||||
BaseEventState e = (BaseEventState)Activator.CreateInstance(knownType, [(NodeState)null]);
|
||||
|
||||
// get the filter which defines the contents of the notification.
|
||||
EventFilter filter = monitoredItem.Status.Filter as EventFilter;
|
||||
|
||||
@@ -48,9 +48,9 @@ public class OPCUAJsonEncoder : IJsonEncoder, IEncoder, IDisposable
|
||||
|
||||
private bool m_leaveOpen;
|
||||
|
||||
private static readonly char[] m_specialChars = new char[7] { '"', '\\', '\n', '\r', '\t', '\b', '\f' };
|
||||
private static readonly char[] m_specialChars = ['"', '\\', '\n', '\r', '\t', '\b', '\f'];
|
||||
|
||||
private static readonly char[] m_substitution = new char[7] { '"', '\\', 'n', 'r', 't', 'b', 'f' };
|
||||
private static readonly char[] m_substitution = ['"', '\\', 'n', 'r', 't', 'b', 'f'];
|
||||
|
||||
public EncodingType EncodingType => EncodingType.Json;
|
||||
|
||||
|
||||
@@ -22,16 +22,13 @@
|
||||
"NotString": "Data type in PLC is not String",
|
||||
"WriteDataLengthMore": "Write length exceeds limit",
|
||||
|
||||
"ERROR01": "Address out of range",
|
||||
"ERROR02": "Invalid return length",
|
||||
"ERROR03": "Data size does not match",
|
||||
"ERROR04": "Data block does not exist",
|
||||
"ERROR05": "Exceeds PDU size",
|
||||
"ERROR06": "Invalid value",
|
||||
"ERROR07": "Function not available",
|
||||
"ERROR08": "Password required",
|
||||
"ERROR09": "Invalid password",
|
||||
"ERROR010": "No password set or cleared",
|
||||
"ERROR5": "Invalid address",
|
||||
"ERROR1": "Hardware error",
|
||||
"ERROR3": "Accessing the object not allowed",
|
||||
"ERROR6": "Data type not supported",
|
||||
"ERROR7": "Data type inconsistent",
|
||||
"ERROR10": "Object does not exist",
|
||||
|
||||
|
||||
"MulWriteError": "Multiple write not supported",
|
||||
"StringLengthReadError": "Consecutive reading of variable-length strings not supported",
|
||||
|
||||
@@ -22,16 +22,13 @@
|
||||
"NotString": "PLC中的数据类型不是String",
|
||||
"WriteDataLengthMore": "写入长度超限",
|
||||
|
||||
"ERROR01": "地址超限",
|
||||
"ERROR02": "返回长度无效",
|
||||
"ERROR03": "数据大小不匹配",
|
||||
"ERROR04": "数据块不存在",
|
||||
"ERROR05": "超出PDU大小",
|
||||
"ERROR06": "无效的值",
|
||||
"ERROR07": "功能不可用",
|
||||
"ERROR08": "需要密码",
|
||||
"ERROR09": "无效密码",
|
||||
"ERROR010": "没有设置密码或已清除",
|
||||
"ERROR5": "无效地址,所需的地址超出此PLC的极限",
|
||||
"ERROR1": "硬件错误",
|
||||
"ERROR3": "对象不允许访问",
|
||||
"ERROR6": "数据类型不支持",
|
||||
"ERROR7": "日期类型不一致",
|
||||
"ERROR10": "对象不存在",
|
||||
|
||||
|
||||
"MulWriteError": "不支持多写",
|
||||
"StringLengthReadError": "不支持变长字符串的连读",
|
||||
|
||||
@@ -0,0 +1,186 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://kimdiego2098.github.io/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using TouchSocket.Core;
|
||||
|
||||
namespace ThingsGateway.Foundation.SiemensS7;
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc/>
|
||||
/// </summary>
|
||||
internal class S7Response : S7Request
|
||||
{
|
||||
/// <summary>
|
||||
/// 错误码
|
||||
/// </summary>
|
||||
public byte? ErrorCode { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc/>
|
||||
/// </summary>
|
||||
internal class S7Message : MessageBase, IResultMessage
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public override int HeaderLength => 4;
|
||||
|
||||
public S7Response Response { get; set; } = new();
|
||||
public SiemensAddress[] Request { get; set; }
|
||||
public S7Send? S7Send { get; set; }
|
||||
|
||||
public override void SendInfo(ISendMessage sendMessage)
|
||||
{
|
||||
S7Send = (sendMessage as S7Send);
|
||||
Request = S7Send.SiemensAddress;
|
||||
}
|
||||
|
||||
public override bool CheckHead<TByteBlock>(ref TByteBlock byteBlock)
|
||||
{
|
||||
byteBlock.Position += 2;
|
||||
BodyLength = byteBlock.ReadUInt16(EndianType.Big) - 4;
|
||||
return true;
|
||||
}
|
||||
|
||||
public override FilterResult CheckBody<TByteBlock>(ref TByteBlock byteBlock)
|
||||
{
|
||||
if (Response.ErrorCode.HasValue)
|
||||
{
|
||||
return FilterResult.Success;
|
||||
}
|
||||
var pos = byteBlock.Position;
|
||||
if (S7Send.Handshake)
|
||||
{
|
||||
if (byteBlock[pos + 1] == 0xD0) // 首次握手0XD0连接确认
|
||||
{
|
||||
return FilterResult.Success;
|
||||
}
|
||||
else
|
||||
{
|
||||
// 其余情况判断错误代码
|
||||
if (byteBlock[pos + 13] + byteBlock[pos + 14] > 0) // 如果错误代码不为0
|
||||
{
|
||||
Response.ErrorCode = byteBlock[pos + 14];
|
||||
this.OperCode = 999;
|
||||
this.ErrorMessage = SiemensS7Resource.Localizer["ReturnError", byteBlock[pos + 13].ToString("X2"), byteBlock[pos + 14].ToString("X2")];
|
||||
return FilterResult.Success;
|
||||
}
|
||||
else
|
||||
{
|
||||
Content = byteBlock.ToArray(byteBlock.Length - 2, 2);
|
||||
this.OperCode = 0;
|
||||
return FilterResult.Success;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//分bit/byte解析
|
||||
if (S7Send.Read)
|
||||
{
|
||||
int length = 0;
|
||||
int itemLen = Request.Length;//驱动只会读取一个项
|
||||
|
||||
//添加错误代码校验
|
||||
// 其余情况判断错误代码
|
||||
if (byteBlock[pos + 13] + byteBlock[pos + 14] > 0) // 如果错误代码不为0
|
||||
{
|
||||
Response.ErrorCode = byteBlock[pos + 14];
|
||||
this.OperCode = 999;
|
||||
this.ErrorMessage = SiemensS7Resource.Localizer["ReturnError", byteBlock[pos + 13].ToString("X2"), byteBlock[pos + 14].ToString("X2")];
|
||||
return FilterResult.Success;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (byteBlock[pos + 16] != itemLen)
|
||||
{
|
||||
this.OperCode = 999;
|
||||
this.ErrorMessage = SiemensS7Resource.Localizer["DataLengthError"];
|
||||
return FilterResult.Success;
|
||||
}
|
||||
|
||||
if (byteBlock.Length < pos + 18)
|
||||
{
|
||||
this.OperCode = 999;
|
||||
this.ErrorMessage = SiemensS7Resource.Localizer["DataLengthError"];
|
||||
return FilterResult.Success;
|
||||
}
|
||||
if (byteBlock[pos + 17] != byte.MaxValue)
|
||||
{
|
||||
this.OperCode = 999;
|
||||
this.ErrorMessage = SiemensS7Resource.Localizer["ValidateDataError", byteBlock[pos + 17], SiemensHelper.GetCpuError(byteBlock[pos + 17])];
|
||||
return FilterResult.Success;
|
||||
}
|
||||
|
||||
//解析读取字节数组
|
||||
for (int index = 0; index < itemLen; index++)
|
||||
{
|
||||
var address = Request[index];
|
||||
length += address.Length;
|
||||
}
|
||||
using ValueByteBlock data = new(length);
|
||||
var dataIndex = pos + 17;
|
||||
for (int index = 0; index < itemLen; index++)
|
||||
{
|
||||
if (byteBlock[dataIndex] != byte.MaxValue)
|
||||
{
|
||||
this.OperCode = 999;
|
||||
this.ErrorMessage = SiemensS7Resource.Localizer["ValidateDataError", byteBlock[pos + 17], SiemensHelper.GetCpuError(byteBlock[pos + 17])];
|
||||
return FilterResult.Success;
|
||||
}
|
||||
|
||||
var address = Request[index];
|
||||
if (byteBlock[dataIndex + 1] == 4)//Bit:3;Byte:4;Counter或者Timer:9
|
||||
{
|
||||
data.Write(byteBlock.Span.Slice(dataIndex + 4, address.Length));
|
||||
dataIndex += address.Length + 3;
|
||||
}
|
||||
else if (byteBlock[dataIndex + 1] == 9)//Counter或者Timer:9
|
||||
{
|
||||
int num = (byteBlock[dataIndex + 2] * 256) + byteBlock[dataIndex + 3];
|
||||
if (num % 3 == 0)
|
||||
{
|
||||
for (int indexCT = 0; indexCT < num / 3; indexCT++)
|
||||
{
|
||||
data.Write(byteBlock.Span.Slice(dataIndex + 5 + (3 * indexCT), 2));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int indexCT = 0; indexCT < num / 5; indexCT++)
|
||||
{
|
||||
data.Write(byteBlock.Span.Slice(dataIndex + 7 + (5 * indexCT), 2));
|
||||
}
|
||||
}
|
||||
dataIndex += num + 4;
|
||||
}
|
||||
}
|
||||
|
||||
this.OperCode = 0;
|
||||
Content = data.ToArray();
|
||||
return FilterResult.Success;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (byteBlock.Length < pos + 18)
|
||||
{
|
||||
this.OperCode = 999;
|
||||
this.ErrorMessage = SiemensS7Resource.Localizer["DataLengthError"];
|
||||
return FilterResult.Success;
|
||||
}
|
||||
if (byteBlock[pos + 17] != byte.MaxValue)
|
||||
{
|
||||
this.OperCode = 999;
|
||||
this.ErrorMessage = SiemensS7Resource.Localizer["ValidateDataError", byteBlock[pos + 17], SiemensHelper.GetCpuError(byteBlock[pos + 17])];
|
||||
return FilterResult.Success;
|
||||
}
|
||||
return FilterResult.Success;
|
||||
}
|
||||
}
|
||||
}
|
||||
187
src/adapter/ThingsGateway.Foundation.SiemensS7/S7/Core/S7Send.cs
Normal file
187
src/adapter/ThingsGateway.Foundation.SiemensS7/S7/Core/S7Send.cs
Normal file
@@ -0,0 +1,187 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://kimdiego2098.github.io/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using TouchSocket.Core;
|
||||
|
||||
namespace ThingsGateway.Foundation.SiemensS7;
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc/>
|
||||
/// </summary>
|
||||
public class S7Request
|
||||
{
|
||||
#region Request
|
||||
|
||||
/// <summary>
|
||||
/// bit位偏移
|
||||
/// </summary>
|
||||
public byte BitCode { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 数据块代码
|
||||
/// </summary>
|
||||
public byte DataCode { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// DB块数据信息
|
||||
/// </summary>
|
||||
public ushort DbBlock { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// IsString,默认是true,如果是char[],需要填写W=false;
|
||||
/// </summary>
|
||||
public bool IsString { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Length
|
||||
/// </summary>
|
||||
public int Length { get; set; }
|
||||
|
||||
public int AddressStart { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 写入数据
|
||||
/// </summary>
|
||||
public byte[] Data { get; set; }
|
||||
|
||||
#endregion Request
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc/>
|
||||
/// </summary>
|
||||
internal class S7Send : ISendMessage
|
||||
{
|
||||
public int Sign { get; set; }
|
||||
|
||||
public int MaxLength => 2048;
|
||||
|
||||
internal SiemensAddress[]? SiemensAddress;
|
||||
internal bool Read;
|
||||
internal bool IsBit;
|
||||
internal bool Handshake;
|
||||
internal byte[] HandshakeBytes;
|
||||
|
||||
public S7Send(SiemensAddress[]? siemensAddress = null, bool? read = null, bool? isBit = null, bool? handshake = null, byte[]? handshakeBytes = default)
|
||||
{
|
||||
SiemensAddress = siemensAddress;
|
||||
Read = read ?? false;
|
||||
IsBit = isBit ?? false;
|
||||
Handshake = handshake ?? false;
|
||||
HandshakeBytes = handshakeBytes;
|
||||
}
|
||||
|
||||
internal void GetReadCommand<TByteBlock>(ref TByteBlock valueByteBlock, SiemensAddress[] siemensAddress) where TByteBlock : IByteBlock
|
||||
{
|
||||
byte len = (byte)siemensAddress.Length;
|
||||
ushort telegramLen = (ushort)(len * 12 + 19);
|
||||
ushort parameterLen = (ushort)(len * 12 + 2);
|
||||
//TPKT
|
||||
valueByteBlock.WriteByte(3);//版本
|
||||
valueByteBlock.WriteByte(0);
|
||||
valueByteBlock.WriteUInt16(telegramLen, EndianType.Big);//长度,item.len*12+19
|
||||
//COTP信息
|
||||
valueByteBlock.WriteByte(2);//长度
|
||||
valueByteBlock.WriteByte(0xf0);//pdu类型
|
||||
valueByteBlock.WriteByte(0x80);//目标引用
|
||||
//header
|
||||
valueByteBlock.WriteByte(0x32);//协议id
|
||||
valueByteBlock.WriteByte(0x01);//请求
|
||||
valueByteBlock.WriteUInt16(0x00, EndianType.Big);//冗余识别
|
||||
valueByteBlock.WriteUInt16(0x01, EndianType.Big);//数据引用
|
||||
valueByteBlock.WriteUInt16(parameterLen, EndianType.Big);//参数长度,item.len*12+2
|
||||
valueByteBlock.WriteUInt16(0x00, EndianType.Big);//数据长度,data.len+4 ,写入时填写,读取时为0
|
||||
valueByteBlock.WriteByte(0x04);//功能码,4 Read Var, 5 Write Var
|
||||
valueByteBlock.WriteByte(len);//Item数量
|
||||
|
||||
//通信项构建
|
||||
for (int index = 0; index < len; index++)
|
||||
{
|
||||
valueByteBlock.WriteByte(0x12);//Var 规范
|
||||
valueByteBlock.WriteByte(0x0a);//剩余的字节长度
|
||||
valueByteBlock.WriteByte(0x10);//Syntax ID
|
||||
|
||||
if (siemensAddress[index].DataCode == (byte)S7WordLength.Counter || siemensAddress[index].DataCode == (byte)S7WordLength.Timer)
|
||||
{
|
||||
valueByteBlock.WriteByte(siemensAddress[index].DataCode);//数据类型
|
||||
}
|
||||
else
|
||||
{
|
||||
valueByteBlock.WriteByte((byte)S7WordLength.Byte);//数据类型
|
||||
}
|
||||
valueByteBlock.WriteUInt16((ushort)siemensAddress[index].Length, EndianType.Big);//读取长度
|
||||
valueByteBlock.WriteUInt16(siemensAddress[index].DbBlock, EndianType.Big);//DB编号
|
||||
valueByteBlock.WriteByte(siemensAddress[index].DataCode);//数据块类型
|
||||
valueByteBlock.WriteByte((byte)(siemensAddress[index].AddressStart / 256 / 256 % 256));//数据块偏移量
|
||||
valueByteBlock.WriteByte((byte)(siemensAddress[index].AddressStart / 256 % 256));//数据块偏移量
|
||||
valueByteBlock.WriteByte((byte)(siemensAddress[index].AddressStart % 256));//数据块偏移量
|
||||
}
|
||||
}
|
||||
|
||||
internal static void GetWriteByteCommand<TByteBlock>(ref TByteBlock valueByteBlock, SiemensAddress address, bool isBit) where TByteBlock : IByteBlock
|
||||
{
|
||||
var data = address.Data;
|
||||
byte len = (byte)data.Length;
|
||||
ushort telegramLen = (ushort)(16 + 19 + len);
|
||||
ushort parameterLen = 12 + 2;
|
||||
//TPKT
|
||||
valueByteBlock.WriteByte(3);//版本
|
||||
valueByteBlock.WriteByte(0);
|
||||
valueByteBlock.WriteUInt16(telegramLen, EndianType.Big);//长度,item.len*12+19
|
||||
//COTP信息
|
||||
valueByteBlock.WriteByte(2);//长度
|
||||
valueByteBlock.WriteByte(0xf0);//pdu类型
|
||||
valueByteBlock.WriteByte(0x80);//目标引用
|
||||
//header
|
||||
valueByteBlock.WriteByte(0x32);//协议id
|
||||
valueByteBlock.WriteByte(0x01);//请求
|
||||
valueByteBlock.WriteUInt16(0x00, EndianType.Big);//冗余识别
|
||||
valueByteBlock.WriteUInt16(0x01, EndianType.Big);//数据引用
|
||||
valueByteBlock.WriteUInt16(parameterLen, EndianType.Big);//参数长度,item.len*12+2
|
||||
valueByteBlock.WriteUInt16((ushort)(4 + len), EndianType.Big);//数据长度,data.len+4 ,写入时填写,读取时为0
|
||||
valueByteBlock.WriteByte(0x05);//功能码,4 Read Var, 5 Write Var
|
||||
valueByteBlock.WriteByte(1);//Item数量
|
||||
|
||||
//写入Item与读取大致相同
|
||||
valueByteBlock.WriteByte(0x12);//Var 规范
|
||||
valueByteBlock.WriteByte(0x0a);//剩余的字节长度
|
||||
valueByteBlock.WriteByte(0x10);//Syntax ID
|
||||
valueByteBlock.WriteByte(isBit ? (byte)S7WordLength.Bit : (byte)S7WordLength.Byte);//数据类型
|
||||
valueByteBlock.WriteUInt16((ushort)len, EndianType.Big);//长度
|
||||
valueByteBlock.WriteUInt16(address.DbBlock, EndianType.Big);//DB编号
|
||||
valueByteBlock.WriteByte(address.DataCode);//数据块类型
|
||||
valueByteBlock.WriteByte((byte)((address.AddressStart + address.BitCode) / 256 / 256));//数据块偏移量
|
||||
valueByteBlock.WriteByte((byte)((address.AddressStart + address.BitCode) / 256));//数据块偏移量
|
||||
valueByteBlock.WriteByte((byte)((address.AddressStart + address.BitCode) % 256));//数据块偏移量
|
||||
|
||||
//后面跟的是写入的数据信息
|
||||
valueByteBlock.WriteByte(0);
|
||||
valueByteBlock.WriteByte((byte)(isBit ? 3 : 4));//Bit:3;Byte:4;Counter或者Timer:9
|
||||
valueByteBlock.WriteUInt16((ushort)(isBit ? len : len * 8), EndianType.Big);
|
||||
valueByteBlock.Write(data);
|
||||
}
|
||||
|
||||
public void Build<TByteBlock>(ref TByteBlock byteBlock) where TByteBlock : IByteBlock
|
||||
{
|
||||
if (Handshake == true)
|
||||
{
|
||||
byteBlock.Write(HandshakeBytes);
|
||||
return;
|
||||
}
|
||||
if (Read == true)
|
||||
{
|
||||
GetReadCommand(ref byteBlock, SiemensAddress);
|
||||
}
|
||||
else
|
||||
{
|
||||
GetWriteByteCommand(ref byteBlock, SiemensAddress[0], IsBit);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -19,34 +19,22 @@ namespace ThingsGateway.Foundation.SiemensS7;
|
||||
/// <summary>
|
||||
/// 西门子PLC地址数据信息
|
||||
/// </summary>
|
||||
public class SiemensAddress
|
||||
public class SiemensAddress : S7Request
|
||||
{
|
||||
/// <summary>
|
||||
/// bit位偏移
|
||||
/// </summary>
|
||||
public byte BitCode { get; set; }
|
||||
public SiemensAddress()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 数据块代码
|
||||
/// </summary>
|
||||
public byte DataCode { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// DB块数据信息
|
||||
/// </summary>
|
||||
public ushort DbBlock { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// IsString,默认是true,如果是char[],需要填写W=false;
|
||||
/// </summary>
|
||||
public bool IsString { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Length
|
||||
/// </summary>
|
||||
public int Length { get; set; }
|
||||
|
||||
public int AddressStart { get; set; }
|
||||
public SiemensAddress(SiemensAddress siemensAddress)
|
||||
{
|
||||
this.AddressStart = siemensAddress.AddressStart;
|
||||
this.BitCode = siemensAddress.BitCode;
|
||||
this.DataCode = siemensAddress.DataCode;
|
||||
this.DbBlock = siemensAddress.DbBlock;
|
||||
this.Data = siemensAddress.Data;
|
||||
this.IsString = siemensAddress.IsString;
|
||||
this.Length = siemensAddress.Length;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string ToString()
|
||||
@@ -88,7 +76,7 @@ public class SiemensAddress
|
||||
return DataCode == (byte)S7Area.DB ? $"DB{DbBlock}.{GetStringAddress(AddressStart)}{(IsString ? ";" : ";W=false;")}" : AddressStart.ToString() + (IsString ? ";" : ";W=false;");
|
||||
}
|
||||
|
||||
private static string GetStringAddress(int addressStart)
|
||||
private string GetStringAddress(int addressStart)
|
||||
{
|
||||
return addressStart % 8 == 0 ? (addressStart / 8).ToString() : $"{addressStart / 8}.{addressStart % 8}";
|
||||
}
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://kimdiego2098.github.io/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace ThingsGateway.Foundation.SiemensS7;
|
||||
|
||||
/// <inheritdoc/>
|
||||
internal class SiemensMessage : MessageBase, IResultMessage
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public override int HeadBytesLength => 4;
|
||||
|
||||
public ReadOnlyMemory<byte> SendBytes { get; set; }
|
||||
|
||||
public override void SendInfo(ReadOnlyMemory<byte> sendBytes)
|
||||
{
|
||||
SendBytes = sendBytes;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool CheckHeadBytes(byte[]? headBytes)
|
||||
{
|
||||
if (headBytes == null || headBytes.Length < 4)
|
||||
BodyLength = 0;
|
||||
int length = (headBytes[2] * 256) + headBytes[3] - 4;
|
||||
if (length < 0)
|
||||
length = 0;
|
||||
BodyLength = length;
|
||||
return headBytes != null && headBytes[0] == 3 && headBytes[1] == 0;
|
||||
}
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://kimdiego2098.github.io/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using TouchSocket.Core;
|
||||
|
||||
namespace ThingsGateway.Foundation.SiemensS7;
|
||||
|
||||
/// <summary>
|
||||
/// SiemensS7DataHandleAdapter,西门子S7数据处理适配器
|
||||
/// </summary>
|
||||
internal class SiemensS7DataHandleAdapter : ReadWriteDevicesSingleStreamDataHandleAdapter<SiemensMessage>
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
protected override AdapterResult UnpackResponse(SiemensMessage request, IByteBlock byteBlock)
|
||||
{
|
||||
var result = new OperResult<AdapterResult>(); // 创建一个操作结果对象
|
||||
if (byteBlock[2] * 256 + byteBlock[3] == 7 || request.SendBytes.Length == 0) // 判断响应中的状态信息是否为7
|
||||
{
|
||||
result = new() { Content = new AdapterResult() { Content = Array.Empty<byte>(), FilterResult = FilterResult.Success } }; // 如果是7,则表示成功,设置操作结果为成功
|
||||
}
|
||||
else
|
||||
{
|
||||
var send = request.SendBytes.Span;
|
||||
// 以请求方为准,分开返回类型校验
|
||||
switch (send[17]) // 根据请求命令中的类型字节进行分析
|
||||
{
|
||||
case 0x04: // 读取类型命令
|
||||
result = SiemensHelper.AnalysisReadByte(send, byteBlock); // 调用辅助方法进行读取数据的解析
|
||||
break;
|
||||
|
||||
case 0x05: // 写入类型命令
|
||||
result = SiemensHelper.AnalysisWrite(byteBlock); // 调用辅助方法进行写入数据的解析
|
||||
break;
|
||||
|
||||
default:
|
||||
// 添加返回代码校验
|
||||
|
||||
if (byteBlock[5] == 0xD0) // 首次握手0XD0连接确认
|
||||
{
|
||||
result = new() { Content = new AdapterResult() { Content = Array.Empty<byte>(), FilterResult = FilterResult.Success } }; // 如果是7,则表示成功,设置操作结果为成功
|
||||
}
|
||||
else
|
||||
{
|
||||
// 其余情况判断错误代码
|
||||
if (byteBlock[17] + byteBlock[18] > 0) // 如果错误代码不为0
|
||||
{
|
||||
result = new(SiemensS7Resource.Localizer["ReturnError", byteBlock[17].ToString("X2"), byteBlock[18].ToString("X2")]) { Content = new AdapterResult() { Content = Array.Empty<byte>(), FilterResult = FilterResult.Success } };
|
||||
}
|
||||
else
|
||||
{
|
||||
result = new() { Content = new AdapterResult() { Content = byteBlock.ToArray(byteBlock.Length - 2, 2), FilterResult = FilterResult.Success } }; // 如果错误代码为0,则设置操作结果为成功
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
request.OperCode = result.OperCode; // 设置请求的操作码
|
||||
request.ErrorMessage = result.ErrorMessage; // 设置请求的错误信息
|
||||
return result.Content; // 返回操作结果的第二部分内容
|
||||
}
|
||||
}
|
||||
@@ -49,7 +49,7 @@ internal static class PackHelper
|
||||
{
|
||||
// 解析SiemensAddress对象
|
||||
var s7Address = SiemensAddress.ParseFrom(it.RegisterAddress);
|
||||
var lastLen = it.DataType.GetByteLength();
|
||||
int lastLen = it.DataType.GetByteLength();
|
||||
|
||||
// 处理特殊情况下的长度
|
||||
if (lastLen <= 0)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user