螺丝枪测试
This commit is contained in:
parent
c67dbfe034
commit
cb17c04d3f
537
RIZO.Admin.WebApi/PLC/Service/PF6ScrewGunService.cs
Normal file
537
RIZO.Admin.WebApi/PLC/Service/PF6ScrewGunService.cs
Normal file
@ -0,0 +1,537 @@
|
||||
using System;
|
||||
using System.Net.Sockets;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
|
||||
namespace RIZO.Admin.WebApi.PLC.Service
|
||||
{
|
||||
/// <summary>
|
||||
/// PF6螺丝枪基础通讯服务(核心功能:TCP连接、心跳、全报文发收测试)
|
||||
/// 适配PF6开放协议V2.5版本,支持所有已定义报文的批量发送与调试
|
||||
/// </summary>
|
||||
public class PF6ScrewGunService : BackgroundService
|
||||
{
|
||||
// TCP通讯核心对象
|
||||
private TcpClient _tcpClient;
|
||||
private NetworkStream _networkStream;
|
||||
|
||||
// 螺丝枪配置(与协议文档一致)
|
||||
private readonly string _screwGunIp = "192.168.1.145"; // 控制器IP(可配置)
|
||||
private readonly int _screwGunPort = 4545; // 协议默认端口(文档明确)
|
||||
private readonly TimeSpan _heartbeatInterval = TimeSpan.FromSeconds(10); // 心跳间隔(文档要求10秒)
|
||||
private readonly TimeSpan _reconnectInterval = TimeSpan.FromSeconds(8); // 重连间隔(延长避免频繁重试)
|
||||
private readonly TimeSpan _testCommandInterval = TimeSpan.FromSeconds(15); // 测试指令间隔(降低频率)
|
||||
private readonly TimeSpan _batchTestInterval = TimeSpan.FromSeconds(30); // 全报文批量测试间隔
|
||||
|
||||
// 协议基础常量(严格遵循PF6开放协议V2.5文档)
|
||||
private const string MessageEndFlag = "00"; // 报文结束标识(文档第1页明确)
|
||||
private const int MessageTotalLength = 20; // 发送报文固定长度(文档"字符总长20字节")
|
||||
private const string HeartbeatMid = "9999"; // 心跳指令MID(文档测试页面确认)
|
||||
private const string TestTightenMid = "0610"; // 测试拧紧指令MID(文档功能按钮确认)
|
||||
private const string HandshakeMid = "0001"; // 握手指令MID(文档初始连接流程)
|
||||
private const string PsetQueryMid = "0005"; // 参数组查询MID(文档PSET查询功能)
|
||||
private const string TightenResultMid = "0620"; // 拧紧结果查询MID(文档明确)
|
||||
private const string ProtocolVersion = "3030"; // 协议版本号(文档报文示例)
|
||||
private const string MessagePrefix = "303020"; // 报文固定前缀(文档2017-12-1交互示例)
|
||||
private const string FillChar = "20"; // 填充字符(文档"20填满字符总长")
|
||||
private const int LinkTimeout = 1000; // 链路超时(文档配置页面1000ms)
|
||||
private const int AppCommandTimeout = 1000; // 应用指令超时(文档配置页面1000ms)
|
||||
private const int MaxRetries = 3; // 最大重试次数(文档配置页面3次)
|
||||
private const int ReceiveBufferSize = 4096; // 接收缓冲区扩大(文档响应报文可能超过1024字节)
|
||||
|
||||
/// <summary>
|
||||
/// 服务核心执行逻辑
|
||||
/// </summary>
|
||||
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
||||
{
|
||||
Console.WriteLine($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss.fff}] PF6螺丝枪通讯服务开始启动...");
|
||||
Console.WriteLine($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss.fff}] 服务支持报文:握手(0001)、心跳(9999)、测试拧紧(0610)、参数组查询(0005)、拧紧结果查询(0620)");
|
||||
_tcpClient = new TcpClient();
|
||||
|
||||
// 服务主循环:断线自动重连(响应停止指令)
|
||||
while (!stoppingToken.IsCancellationRequested)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 1. 建立TCP连接(带重试)
|
||||
bool connectSuccess = await ConnectWithRetryAsync(stoppingToken);
|
||||
if (!connectSuccess)
|
||||
{
|
||||
Console.WriteLine($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss.fff}] 连接重试失败,等待下一轮重连");
|
||||
await Task.Delay(_reconnectInterval, stoppingToken);
|
||||
continue;
|
||||
}
|
||||
|
||||
// 2. 发送握手报文并确认(协议必须步骤)
|
||||
bool handshakeSuccess = await PerformHandshakeAsync(stoppingToken);
|
||||
if (!handshakeSuccess)
|
||||
{
|
||||
Console.WriteLine($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss.fff}] 握手失败,关闭连接准备重连");
|
||||
DisconnectFromScrewGun();
|
||||
await Task.Delay(_reconnectInterval, stoppingToken);
|
||||
continue;
|
||||
}
|
||||
|
||||
// 3. 并行执行:心跳维持 + 全报文批量测试 + 常规测试指令
|
||||
var taskArray = new[]
|
||||
{
|
||||
KeepHeartbeatContinuousAsync(stoppingToken),
|
||||
SendBatchAllMessagesTestAsync(stoppingToken),
|
||||
SendTestCommandAndReceiveResultAsync(stoppingToken)
|
||||
};
|
||||
|
||||
await Task.WhenAny(taskArray);
|
||||
Console.WriteLine($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss.fff}] 核心任务退出,准备重连");
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
Console.WriteLine($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss.fff}] PF6螺丝枪通讯服务收到停止指令,正在退出...");
|
||||
break;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss.fff}] PF6螺丝枪通讯服务运行异常,准备重连:{ex.Message}");
|
||||
}
|
||||
finally
|
||||
{
|
||||
DisconnectFromScrewGun();
|
||||
Console.WriteLine($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss.fff}] 当前连接已断开,等待重连...");
|
||||
if (!stoppingToken.IsCancellationRequested)
|
||||
{
|
||||
await Task.Delay(_reconnectInterval, stoppingToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Console.WriteLine($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss.fff}] PF6螺丝枪通讯服务已停止");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 带重试机制的TCP连接(遵循协议重试次数配置)
|
||||
/// </summary>
|
||||
private async Task<bool> ConnectWithRetryAsync(CancellationToken stoppingToken)
|
||||
{
|
||||
int retryCount = 0;
|
||||
while (retryCount < MaxRetries && !stoppingToken.IsCancellationRequested)
|
||||
{
|
||||
try
|
||||
{
|
||||
Console.WriteLine($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss.fff}] 第{retryCount + 1}次尝试连接螺丝枪:{_screwGunIp}:{_screwGunPort}");
|
||||
|
||||
// 重置TcpClient状态
|
||||
if (_tcpClient.Connected)
|
||||
{
|
||||
_tcpClient.Close();
|
||||
}
|
||||
_tcpClient?.Dispose();
|
||||
_tcpClient = new TcpClient();
|
||||
_tcpClient.ReceiveTimeout = LinkTimeout;
|
||||
_tcpClient.SendTimeout = LinkTimeout;
|
||||
|
||||
// 建立连接(设置连接超时,避免无限等待)
|
||||
var connectTask = _tcpClient.ConnectAsync(_screwGunIp, _screwGunPort);
|
||||
var timeoutTask = Task.Delay(LinkTimeout * 2, stoppingToken);
|
||||
var completedTask = await Task.WhenAny(connectTask, timeoutTask);
|
||||
|
||||
if (completedTask == timeoutTask)
|
||||
{
|
||||
throw new TimeoutException("连接超时(超过2000ms)");
|
||||
}
|
||||
|
||||
// 初始化网络流(配置超时参数与协议一致)
|
||||
_networkStream = _tcpClient.GetStream();
|
||||
_networkStream.ReadTimeout = AppCommandTimeout;
|
||||
_networkStream.WriteTimeout = AppCommandTimeout;
|
||||
|
||||
Console.WriteLine($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss.fff}] 螺丝枪TCP连接成功!(链路超时:{LinkTimeout}ms,指令超时:{AppCommandTimeout}ms)");
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
retryCount++;
|
||||
Console.WriteLine($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss.fff}] 第{retryCount}次连接失败:{ex.Message}");
|
||||
if (retryCount < MaxRetries)
|
||||
{
|
||||
await Task.Delay(1000 * retryCount, stoppingToken); // 重试间隔递增(1s→2s→3s)
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 协议握手流程(发送MID=0001,等待控制器确认)
|
||||
/// </summary>
|
||||
private async Task<bool> PerformHandshakeAsync(CancellationToken stoppingToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 构建握手报文(严格遵循文档格式)
|
||||
var handshakeMessage = BuildStandardMessage(HandshakeMid);
|
||||
Console.WriteLine($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss.fff}] 准备发送握手报文(MID=0001),报文内容:{handshakeMessage}");
|
||||
|
||||
// 发送握手报文(带重试)
|
||||
bool sendSuccess = await SendMessageWithRetryAsync(handshakeMessage, stoppingToken, "握手");
|
||||
if (!sendSuccess)
|
||||
{
|
||||
Console.WriteLine($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss.fff}] 握手报文发送失败(已重试{MaxRetries}次)");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 接收握手响应(文档响应报文可能较长,需完整接收)
|
||||
Console.WriteLine($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss.fff}] 等待握手响应(超时:{AppCommandTimeout}ms)");
|
||||
var handshakeResponse = await ReceiveMessageAsync(stoppingToken, "握手");
|
||||
|
||||
// 校验响应有效性
|
||||
if (string.IsNullOrEmpty(handshakeResponse))
|
||||
{
|
||||
Console.WriteLine($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss.fff}] 握手响应为空,无效");
|
||||
return false;
|
||||
}
|
||||
if (!handshakeResponse.EndsWith(MessageEndFlag))
|
||||
{
|
||||
Console.WriteLine($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss.fff}] 握手响应未包含结束标识({MessageEndFlag}),响应内容:{handshakeResponse}");
|
||||
return false;
|
||||
}
|
||||
|
||||
Console.WriteLine($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss.fff}] 握手成功!响应报文:{handshakeResponse}(长度:{handshakeResponse.Length})");
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss.fff}] 握手过程异常:{ex.Message}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 持续发送心跳(协议要求10秒间隔,无响应需重连)
|
||||
/// </summary>
|
||||
private async Task KeepHeartbeatContinuousAsync(CancellationToken stoppingToken)
|
||||
{
|
||||
Console.WriteLine($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss.fff}] 心跳任务启动(间隔:{_heartbeatInterval.TotalSeconds}秒),仅确保发送成功,不强制要求响应");
|
||||
while (!stoppingToken.IsCancellationRequested && _tcpClient.Connected)
|
||||
{
|
||||
try
|
||||
{
|
||||
var heartbeatMessage = BuildStandardMessage(HeartbeatMid);
|
||||
bool sendSuccess = await SendMessageWithRetryAsync(heartbeatMessage, stoppingToken, "心跳");
|
||||
if (sendSuccess)
|
||||
{
|
||||
Console.WriteLine($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss.fff}] 心跳报文发送成功(MID=9999),报文内容:{heartbeatMessage}");
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss.fff}] 心跳报文发送失败(已重试{MaxRetries}次),终止心跳任务");
|
||||
break;
|
||||
}
|
||||
|
||||
await Task.Delay(_heartbeatInterval, stoppingToken);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss.fff}] 心跳交互异常,终止心跳任务:{ex.Message}");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 常规测试指令发送与响应接收(MID=0610)
|
||||
/// </summary>
|
||||
private async Task SendTestCommandAndReceiveResultAsync(CancellationToken stoppingToken)
|
||||
{
|
||||
Console.WriteLine($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss.fff}] 常规测试任务启动(间隔:{_testCommandInterval.TotalSeconds}秒,MID=0610)");
|
||||
while (!stoppingToken.IsCancellationRequested && _tcpClient.Connected)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 构建测试拧紧指令(额外数据"0101"为测试参数)
|
||||
var testMessage = BuildStandardMessage(TestTightenMid, "0101");
|
||||
Console.WriteLine($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss.fff}] 准备发送常规测试指令(MID=0610),报文内容:{testMessage}");
|
||||
|
||||
// 发送指令(带重试)
|
||||
bool sendSuccess = await SendMessageWithRetryAsync(testMessage, stoppingToken, "常规测试拧紧");
|
||||
if (!sendSuccess)
|
||||
{
|
||||
Console.WriteLine($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss.fff}] 常规测试指令发送失败(已重试{MaxRetries}次),延迟5秒重试");
|
||||
await Task.Delay(5000, stoppingToken);
|
||||
continue;
|
||||
}
|
||||
|
||||
// 接收响应(文档响应可能包含多个字段,需完整读取)
|
||||
var testResponse = await ReceiveMessageAsync(stoppingToken, "常规测试拧紧");
|
||||
Console.WriteLine($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss.fff}] 常规测试指令响应成功!响应内容:{testResponse}(长度:{testResponse.Length})");
|
||||
|
||||
await Task.Delay(_testCommandInterval, stoppingToken);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss.fff}] 常规测试指令交互异常,延迟后重试:{ex.Message}");
|
||||
await Task.Delay(3000, stoppingToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 批量发送所有已定义报文(用于完整调试,包含所有功能)
|
||||
/// </summary>
|
||||
private async Task SendBatchAllMessagesTestAsync(CancellationToken stoppingToken)
|
||||
{
|
||||
Console.WriteLine($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss.fff}] 全报文批量测试任务启动(间隔:{_batchTestInterval.TotalSeconds}秒)");
|
||||
while (!stoppingToken.IsCancellationRequested && _tcpClient.Connected)
|
||||
{
|
||||
try
|
||||
{
|
||||
Console.WriteLine($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss.fff}] ============== 开始全报文批量测试 ==============");
|
||||
|
||||
// 1. 参数组查询(MID=0005,查询全部参数组,额外数据为空)
|
||||
await SendSingleMessageWithResponseAsync(PsetQueryMid, "", "参数组查询(MID=0005)", stoppingToken);
|
||||
await Task.Delay(2000, stoppingToken); // 间隔2秒,避免报文拥堵
|
||||
|
||||
// 2. 参数组查询(MID=0005,查询指定参数组ID=01)
|
||||
await SendSingleMessageWithResponseAsync(PsetQueryMid, "01", "参数组查询(MID=0005,指定ID=01)", stoppingToken);
|
||||
await Task.Delay(2000, stoppingToken);
|
||||
|
||||
// 3. 拧紧结果查询(MID=0620,查询全部结果)
|
||||
await SendSingleMessageWithResponseAsync(TightenResultMid, "", "拧紧结果查询(MID=0620)", stoppingToken);
|
||||
await Task.Delay(2000, stoppingToken);
|
||||
|
||||
// 4. 拧紧结果查询(MID=0620,查询指定SN=0001)
|
||||
await SendSingleMessageWithResponseAsync(TightenResultMid, "0001", "拧紧结果查询(MID=0620,指定SN=0001)", stoppingToken);
|
||||
await Task.Delay(2000, stoppingToken);
|
||||
|
||||
// 5. 测试拧紧(MID=0610,额外参数=0202)
|
||||
await SendSingleMessageWithResponseAsync(TestTightenMid, "0202", "测试拧紧(MID=0610,参数=0202)", stoppingToken);
|
||||
|
||||
Console.WriteLine($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss.fff}] ============== 全报文批量测试结束 ==============\n");
|
||||
await Task.Delay(_batchTestInterval, stoppingToken);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss.fff}] 全报文批量测试异常:{ex.Message}\n");
|
||||
await Task.Delay(10000, stoppingToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 发送单个报文并接收响应(封装通用逻辑,简化批量测试)
|
||||
/// </summary>
|
||||
private async Task SendSingleMessageWithResponseAsync(string mid, string extraData, string messageDesc, CancellationToken stoppingToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 构建报文
|
||||
var message = BuildStandardMessage(mid, extraData);
|
||||
Console.WriteLine($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss.fff}] 准备发送 {messageDesc},报文内容:{message}");
|
||||
|
||||
// 发送报文
|
||||
bool sendSuccess = await SendMessageWithRetryAsync(message, stoppingToken, messageDesc);
|
||||
if (!sendSuccess)
|
||||
{
|
||||
Console.WriteLine($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss.fff}] {messageDesc} 发送失败(已重试{MaxRetries}次)");
|
||||
return;
|
||||
}
|
||||
|
||||
// 接收响应
|
||||
var response = await ReceiveMessageAsync(stoppingToken, messageDesc);
|
||||
Console.WriteLine($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss.fff}] {messageDesc} 响应成功!响应内容:{response}(长度:{response.Length})");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss.fff}] {messageDesc} 交互异常:{ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 构建标准PF6协议报文(严格遵循文档格式要求,增加参数校验)
|
||||
/// </summary>
|
||||
private string BuildStandardMessage(string mid, string extraData = "")
|
||||
{
|
||||
// 前置严格校验,避免无效报文
|
||||
if (string.IsNullOrEmpty(mid))
|
||||
{
|
||||
throw new ArgumentNullException(nameof(mid), "指令MID不能为空(PF6协议要求4位字符)");
|
||||
}
|
||||
if (mid.Length != 4)
|
||||
{
|
||||
throw new ArgumentException($"MID必须为4位字符(当前MID:{mid},长度:{mid.Length})", nameof(mid));
|
||||
}
|
||||
// 额外数据避免过长,防止填充后超出长度限制
|
||||
if (!string.IsNullOrEmpty(extraData) && extraData.Length > 8)
|
||||
{
|
||||
throw new ArgumentException("额外数据长度不能超过8位(避免超出报文总长度限制)", nameof(extraData));
|
||||
}
|
||||
|
||||
var messageBuilder = new StringBuilder();
|
||||
// 拼接格式:固定前缀 + MID + 版本号 + 额外数据 + 填充符 + 结束标识
|
||||
messageBuilder.Append(MessagePrefix)
|
||||
.Append(mid)
|
||||
.Append(ProtocolVersion)
|
||||
.Append(extraData);
|
||||
|
||||
// 补全填充字符(确保总长达到20-2=18字节,预留结束标识位置)
|
||||
while (messageBuilder.Length < MessageTotalLength - MessageEndFlag.Length)
|
||||
{
|
||||
messageBuilder.Append(FillChar);
|
||||
}
|
||||
|
||||
// 追加结束标识
|
||||
messageBuilder.Append(MessageEndFlag);
|
||||
|
||||
// 长度校验(严格保证20字节,协议强制要求)
|
||||
if (messageBuilder.Length != MessageTotalLength)
|
||||
{
|
||||
throw new InvalidOperationException($"构建的报文长度不符合PF6协议要求,预期{MessageTotalLength}位,实际{messageBuilder.Length}位,报文内容:{messageBuilder.ToString()}");
|
||||
}
|
||||
|
||||
Console.WriteLine($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss.fff}] 报文构建成功(MID={mid}),长度:{messageBuilder.Length}位,内容:{messageBuilder.ToString()}");
|
||||
return messageBuilder.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 发送报文(带重试机制,遵循协议重试次数,增加报文描述日志)
|
||||
/// </summary>
|
||||
private async Task<bool> SendMessageWithRetryAsync(string message, CancellationToken stoppingToken, string messageDesc)
|
||||
{
|
||||
int retryCount = 0;
|
||||
while (retryCount < MaxRetries && !stoppingToken.IsCancellationRequested && _tcpClient.Connected)
|
||||
{
|
||||
try
|
||||
{
|
||||
var sendData = Encoding.ASCII.GetBytes(message);
|
||||
await _networkStream.WriteAsync(sendData, 0, sendData.Length, stoppingToken);
|
||||
await _networkStream.FlushAsync(stoppingToken);
|
||||
Console.WriteLine($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss.fff}] {messageDesc} 报文发送成功(字节数:{sendData.Length})");
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
retryCount++;
|
||||
Console.WriteLine($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss.fff}] {messageDesc} 报文发送重试{retryCount}次:{ex.Message}");
|
||||
if (retryCount < MaxRetries)
|
||||
{
|
||||
await Task.Delay(500, stoppingToken); // 每次重试间隔500ms
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 接收报文(支持动态长度,处理协议响应格式,增加报文描述日志)
|
||||
/// </summary>
|
||||
private async Task<string> ReceiveMessageAsync(CancellationToken stoppingToken, string messageDesc)
|
||||
{
|
||||
if (!_tcpClient.Connected || _networkStream == null)
|
||||
{
|
||||
throw new InvalidOperationException("未与螺丝枪建立有效TCP连接,无法接收报文");
|
||||
}
|
||||
|
||||
var receiveBuffer = new byte[ReceiveBufferSize];
|
||||
var totalBytesRead = 0;
|
||||
var responseBuilder = new StringBuilder();
|
||||
|
||||
try
|
||||
{
|
||||
// 循环读取直到收到结束标识或超时
|
||||
while (!stoppingToken.IsCancellationRequested)
|
||||
{
|
||||
var bytesRead = await _networkStream.ReadAsync(receiveBuffer, 0, receiveBuffer.Length, stoppingToken);
|
||||
if (bytesRead == 0)
|
||||
{
|
||||
throw new SocketException((int)SocketError.ConnectionReset);
|
||||
}
|
||||
|
||||
totalBytesRead += bytesRead;
|
||||
var segment = Encoding.ASCII.GetString(receiveBuffer, 0, bytesRead).TrimEnd('\0', ' ');
|
||||
responseBuilder.Append(segment);
|
||||
|
||||
Console.WriteLine($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss.fff}] 接收{messageDesc}响应片段,读取字节数:{bytesRead},累计字节数:{totalBytesRead},片段内容:{segment}");
|
||||
|
||||
// 检查是否包含结束标识(协议结束标志"00")
|
||||
if (responseBuilder.ToString().EndsWith(MessageEndFlag))
|
||||
{
|
||||
Console.WriteLine($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss.fff}] 接收{messageDesc}响应完成(已检测到结束标识{MessageEndFlag})");
|
||||
break;
|
||||
}
|
||||
|
||||
// 防止缓冲区溢出
|
||||
if (totalBytesRead >= ReceiveBufferSize)
|
||||
{
|
||||
Console.WriteLine($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss.fff}] 接收{messageDesc}响应超过缓冲区上限({ReceiveBufferSize}字节),停止读取");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
var finalResponse = responseBuilder.ToString().TrimEnd('\0', ' ');
|
||||
Console.WriteLine($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss.fff}] {messageDesc} 响应处理完成,最终内容长度:{finalResponse.Length}");
|
||||
return finalResponse;
|
||||
}
|
||||
catch (TimeoutException)
|
||||
{
|
||||
// 心跳响应可能超时,返回已接收数据
|
||||
var partialData = responseBuilder.ToString().TrimEnd('\0', ' ');
|
||||
Console.WriteLine($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss.fff}] 接收{messageDesc}响应超时,已接收部分数据:{partialData}(长度:{partialData.Length})");
|
||||
return partialData;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 构建PF6协议参数组查询报文(MID=0005,封装专用方法,提高可读性)
|
||||
/// </summary>
|
||||
private string BuildPsetQueryMessage(string psetId = "")
|
||||
{
|
||||
Console.WriteLine($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss.fff}] 构建参数组查询报文,参数组ID:{(string.IsNullOrEmpty(psetId) ? "全部" : psetId)}");
|
||||
return BuildStandardMessage(PsetQueryMid, psetId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 构建PF6协议拧紧结果查询报文(MID=0620,封装专用方法,提高可读性)
|
||||
/// </summary>
|
||||
private string BuildTightenResultQueryMessage(string snCode = "")
|
||||
{
|
||||
Console.WriteLine($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss.fff}] 构建拧紧结果查询报文,产品SN:{(string.IsNullOrEmpty(snCode) ? "全部" : snCode)}");
|
||||
return BuildStandardMessage(TightenResultMid, snCode); // 绑定正确MID=0620,支持自定义SN码
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 断开连接并释放资源(增加详细日志,避免资源泄漏)
|
||||
/// </summary>
|
||||
private void DisconnectFromScrewGun()
|
||||
{
|
||||
try
|
||||
{
|
||||
Console.WriteLine($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss.fff}] 准备断开与螺丝枪的连接,释放相关资源");
|
||||
|
||||
_networkStream?.Dispose();
|
||||
_networkStream = null;
|
||||
|
||||
if (_tcpClient.Connected)
|
||||
{
|
||||
_tcpClient.Close();
|
||||
Console.WriteLine($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss.fff}] TCP连接已主动关闭");
|
||||
}
|
||||
_tcpClient?.Dispose();
|
||||
|
||||
Console.WriteLine($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss.fff}] 资源释放完成");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss.fff}] 断开连接时发生异常:{ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 释放资源(重写BackgroundService的Dispose方法)
|
||||
/// </summary>
|
||||
public override void Dispose()
|
||||
{
|
||||
DisconnectFromScrewGun();
|
||||
base.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -115,6 +115,9 @@ builder.Services.AddLocalization(options => options.ResourcesPath = "");
|
||||
//builder.Services.AddHostedService<PlcIntoStationService_OP70_01>();
|
||||
//builder.Services.AddHostedService<PlcIntoStationService_OP80_01>();
|
||||
|
||||
//螺丝枪服务注册
|
||||
//builder.Services.AddHostedService<PF6ScrewGunService>();
|
||||
|
||||
// 在应用程序启动的最开始处调用
|
||||
var app = builder.Build();
|
||||
InternalApp.ServiceProvider = app.Services;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user