✨ feat(server): 添加静态状态页与调试入口
- 将状态页、调试页改为 `wwwroot` 静态资源 - 补充调试配置接口与前端脚本 - 为兼容层、规划层和运行时补充日志 - 更新集成测试覆盖新入口
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
using System.Net.Sockets;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Flyshot.Runtime.Fanuc.Protocol;
|
||||
|
||||
@@ -114,6 +115,7 @@ public sealed class FanucStateClient : IDisposable
|
||||
{
|
||||
private readonly object _stateLock = new();
|
||||
private readonly FanucStateClientOptions _options;
|
||||
private readonly ILogger<FanucStateClient>? _logger;
|
||||
private TcpClient? _tcpClient;
|
||||
private NetworkStream? _stream;
|
||||
private CancellationTokenSource? _receiveCts;
|
||||
@@ -130,7 +132,7 @@ public sealed class FanucStateClient : IDisposable
|
||||
/// 使用默认状态通道参数初始化客户端。
|
||||
/// </summary>
|
||||
public FanucStateClient()
|
||||
: this(new FanucStateClientOptions())
|
||||
: this(new FanucStateClientOptions(), null)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -139,10 +141,21 @@ public sealed class FanucStateClient : IDisposable
|
||||
/// </summary>
|
||||
/// <param name="options">超时和重连参数。</param>
|
||||
public FanucStateClient(FanucStateClientOptions options)
|
||||
: this(options, null)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 使用指定状态通道参数和日志记录器初始化客户端。
|
||||
/// </summary>
|
||||
/// <param name="options">超时和重连参数。</param>
|
||||
/// <param name="logger">日志记录器;允许 null。</param>
|
||||
public FanucStateClient(FanucStateClientOptions options, ILogger<FanucStateClient>? logger)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(options);
|
||||
ValidateOptions(options);
|
||||
_options = options;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -170,6 +183,8 @@ public sealed class FanucStateClient : IDisposable
|
||||
throw new InvalidOperationException("状态通道已经连接,请先 Disconnect。");
|
||||
}
|
||||
|
||||
_logger?.LogInformation("StateClient ConnectAsync: {Ip}:{Port}", ip, port);
|
||||
|
||||
_receiveCts = new CancellationTokenSource();
|
||||
using var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, _receiveCts.Token);
|
||||
|
||||
@@ -187,8 +202,9 @@ public sealed class FanucStateClient : IDisposable
|
||||
{
|
||||
await OpenConnectionAsync(ip, port, linkedCts.Token).ConfigureAwait(false);
|
||||
}
|
||||
catch
|
||||
catch (Exception exception)
|
||||
{
|
||||
_logger?.LogError(exception, "StateClient 连接失败: {Ip}:{Port}", ip, port);
|
||||
CloseCurrentConnection();
|
||||
lock (_stateLock)
|
||||
{
|
||||
@@ -203,6 +219,8 @@ public sealed class FanucStateClient : IDisposable
|
||||
_receiveTask = Task.Run(
|
||||
() => ReceiveAndReconnectLoopAsync(ip, port, _receiveCts.Token),
|
||||
_receiveCts.Token);
|
||||
|
||||
_logger?.LogInformation("StateClient 已连接并启动接收循环: {Ip}:{Port}", ip, port);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -212,6 +230,7 @@ public sealed class FanucStateClient : IDisposable
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
_logger?.LogInformation("StateClient Disconnect");
|
||||
Shutdown(clearLatestFrame: true);
|
||||
}
|
||||
|
||||
@@ -268,6 +287,7 @@ public sealed class FanucStateClient : IDisposable
|
||||
private async Task ReceiveAndReconnectLoopAsync(string ip, int port, CancellationToken cancellationToken)
|
||||
{
|
||||
var reconnectDelay = _options.ReconnectInitialDelay;
|
||||
_logger?.LogInformation("StateClient 接收循环启动: {Ip}:{Port}", ip, port);
|
||||
|
||||
while (!cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
@@ -278,14 +298,17 @@ public sealed class FanucStateClient : IDisposable
|
||||
}
|
||||
catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
_logger?.LogInformation("StateClient 接收循环正常取消");
|
||||
return;
|
||||
}
|
||||
catch (TimeoutException ex)
|
||||
{
|
||||
_logger?.LogWarning(ex, "StateClient 接收超时");
|
||||
MarkReceiveFailure(FanucStateConnectionState.TimedOut, ex.Message);
|
||||
}
|
||||
catch (Exception ex) when (ex is IOException or InvalidDataException or SocketException or ObjectDisposedException)
|
||||
{
|
||||
_logger?.LogWarning(ex, "StateClient 连接异常,准备重连");
|
||||
MarkReceiveFailure(FanucStateConnectionState.Reconnecting, ex.Message);
|
||||
}
|
||||
|
||||
@@ -312,6 +335,8 @@ public sealed class FanucStateClient : IDisposable
|
||||
}
|
||||
|
||||
var buffer = new byte[FanucStateProtocol.StateFrameLength];
|
||||
long frameCount = 0;
|
||||
FanucStateFrame? lastLoggedFrame = null;
|
||||
|
||||
while (!cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
@@ -325,6 +350,26 @@ public sealed class FanucStateClient : IDisposable
|
||||
_connectionState = FanucStateConnectionState.Connected;
|
||||
_lastErrorMessage = null;
|
||||
}
|
||||
|
||||
frameCount++;
|
||||
|
||||
// 仅在状态变化或首次接收时记录 Info,避免高频日志。
|
||||
if (lastLoggedFrame is null
|
||||
|| lastLoggedFrame.CartesianPose[0] != frame.CartesianPose[0]
|
||||
|| !lastLoggedFrame.RawTailWords.SequenceEqual(frame.RawTailWords))
|
||||
{
|
||||
_logger?.LogInformation(
|
||||
"StateClient 收到状态帧: pose=[{X:F1}, {Y:F1}, {Z:F1}], tail=[{Tail}]",
|
||||
frame.CartesianPose[0],
|
||||
frame.CartesianPose[1],
|
||||
frame.CartesianPose[2],
|
||||
string.Join(", ", frame.RawTailWords));
|
||||
lastLoggedFrame = frame;
|
||||
}
|
||||
else if (frameCount % 1000 == 0)
|
||||
{
|
||||
_logger?.LogDebug("StateClient 已接收 {Count} 个状态帧", frameCount);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -367,6 +412,8 @@ public sealed class FanucStateClient : IDisposable
|
||||
var tcpClient = new TcpClient { NoDelay = true };
|
||||
try
|
||||
{
|
||||
_logger?.LogInformation("StateClient 正在连接 {Ip}:{Port}...", ip, port);
|
||||
|
||||
using var timeoutCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
|
||||
timeoutCts.CancelAfter(_options.ConnectTimeout);
|
||||
await tcpClient.ConnectAsync(ip, port, timeoutCts.Token).ConfigureAwait(false);
|
||||
@@ -378,14 +425,18 @@ public sealed class FanucStateClient : IDisposable
|
||||
_lastConnectedAt = DateTimeOffset.UtcNow;
|
||||
_connectionState = FanucStateConnectionState.Connected;
|
||||
}
|
||||
|
||||
_logger?.LogInformation("StateClient 已连接到 {Ip}:{Port}", ip, port);
|
||||
}
|
||||
catch (OperationCanceledException) when (!cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
_logger?.LogWarning("StateClient 连接 {Ip}:{Port} 超时", ip, port);
|
||||
tcpClient.Dispose();
|
||||
throw new TimeoutException("状态通道建连超时。");
|
||||
}
|
||||
catch
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger?.LogWarning(ex, "StateClient 连接 {Ip}:{Port} 失败", ip, port);
|
||||
tcpClient.Dispose();
|
||||
throw;
|
||||
}
|
||||
@@ -409,6 +460,12 @@ public sealed class FanucStateClient : IDisposable
|
||||
_connectionState = FanucStateConnectionState.Reconnecting;
|
||||
}
|
||||
|
||||
_logger?.LogInformation(
|
||||
"StateClient 将在 {Delay}ms 后尝试重连 {Ip}:{Port}...",
|
||||
nextDelay.TotalMilliseconds,
|
||||
ip,
|
||||
port);
|
||||
|
||||
await Task.Delay(nextDelay, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
lock (_stateLock)
|
||||
@@ -419,6 +476,11 @@ public sealed class FanucStateClient : IDisposable
|
||||
try
|
||||
{
|
||||
await OpenConnectionAsync(ip, port, cancellationToken).ConfigureAwait(false);
|
||||
_logger?.LogInformation(
|
||||
"StateClient 重连成功: {Ip}:{Port}, 累计重连次数={Count}",
|
||||
ip,
|
||||
port,
|
||||
_reconnectAttemptCount);
|
||||
return _options.ReconnectInitialDelay;
|
||||
}
|
||||
catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
|
||||
@@ -434,6 +496,13 @@ public sealed class FanucStateClient : IDisposable
|
||||
_lastErrorMessage = ex.Message;
|
||||
}
|
||||
|
||||
_logger?.LogWarning(
|
||||
ex,
|
||||
"StateClient 重连失败: {Ip}:{Port}, 下次等待={NextDelay}ms",
|
||||
ip,
|
||||
port,
|
||||
nextDelay.TotalMilliseconds * 2);
|
||||
|
||||
nextDelay = IncreaseReconnectDelay(nextDelay);
|
||||
}
|
||||
}
|
||||
@@ -470,6 +539,8 @@ public sealed class FanucStateClient : IDisposable
|
||||
_connectionState = state;
|
||||
_lastErrorMessage = message;
|
||||
}
|
||||
|
||||
_logger?.LogWarning("StateClient 接收失败: state={State}, message={Message}", state, message);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
Reference in New Issue
Block a user