diff --git a/src/Flyshot.Runtime.Fanuc/FanucControllerRuntime.cs b/src/Flyshot.Runtime.Fanuc/FanucControllerRuntime.cs
index bc87462..45a609e 100644
--- a/src/Flyshot.Runtime.Fanuc/FanucControllerRuntime.cs
+++ b/src/Flyshot.Runtime.Fanuc/FanucControllerRuntime.cs
@@ -39,23 +39,49 @@ public sealed class FanucControllerRuntime : IControllerRuntime, IDisposable
/// 初始化 FANUC 控制器运行时。
///
/// 运行时日志记录器。
+ /// FANUC 10012 命令通道客户端日志记录器。
+ /// FANUC 10010 状态通道客户端日志记录器。
/// J519 客户端日志记录器。
- public FanucControllerRuntime(ILogger logger, ILogger j519Logger)
+ public FanucControllerRuntime(
+ ILogger logger,
+ ILogger commandLogger,
+ ILogger stateLogger,
+ ILogger j519Logger)
{
ArgumentNullException.ThrowIfNull(logger);
+ ArgumentNullException.ThrowIfNull(commandLogger);
+ ArgumentNullException.ThrowIfNull(stateLogger);
ArgumentNullException.ThrowIfNull(j519Logger);
- _commandClient = new FanucCommandClient();
- _stateClient = new FanucStateClient();
+ _commandClient = new FanucCommandClient(commandLogger);
+ _stateClient = new FanucStateClient(new FanucStateClientOptions(), stateLogger);
_j519Client = new FanucJ519Client(j519Logger);
_logger = logger;
}
+ ///
+ /// 初始化 FANUC 控制器运行时,并仅显式传入运行时与 J519 日志记录器。
+ ///
+ /// 运行时日志记录器。
+ /// J519 客户端日志记录器。
+ public FanucControllerRuntime(ILogger logger, ILogger j519Logger)
+ : this(
+ logger,
+ NullLogger.Instance,
+ NullLogger.Instance,
+ j519Logger)
+ {
+ }
+
///
/// 初始化一份无日志的 FANUC 控制器运行时,供离线测试或手工构造场景使用。
///
public FanucControllerRuntime()
- : this(NullLogger.Instance, NullLogger.Instance)
+ : this(
+ NullLogger.Instance,
+ NullLogger.Instance,
+ NullLogger.Instance,
+ NullLogger.Instance)
{
}
@@ -737,18 +763,20 @@ public sealed class FanucControllerRuntime : IControllerRuntime, IDisposable
sample.TrajectoryTime);
}
}
- if (_j519Client.EndCommandStreamAfterQueueDrained && commands.Count > 0)
- {
- // 正常结束命令流时追加独立结束帧,避免把最后一个真实轨迹采样点和结束语义混在一起。
- commands.Add(new FanucJ519Command(
- sequence: 0,
- targetJoints: commands[^1].TargetJoints,
- lastData: 1,
- writeIoType: 2,
- writeIoIndex: 1,
- writeIoMask: 0,
- writeIoValue: 0));
- }
+ //TODO 不进行结束,一致发送最后一帧
+
+ // if (_j519Client.EndCommandStreamAfterQueueDrained && commands.Count > 0)
+ // {
+ // // 正常结束命令流时追加独立结束帧,避免把最后一个真实轨迹采样点和结束语义混在一起。
+ // commands.Add(new FanucJ519Command(
+ // sequence: 0,
+ // targetJoints: commands[^1].TargetJoints,
+ // lastData: 1,
+ // writeIoType: 2,
+ // writeIoIndex: 1,
+ // writeIoMask: 0,
+ // writeIoValue: 0));
+ // }
TryWriteDenseSendArtifacts(result, outputDir, sentJointRows, sentTimingRows, sentJerkRows, triggerBindings);
// 上层只负责生成完整目标序列,真实出队节拍交给 J519 状态包驱动。
diff --git a/src/Flyshot.Runtime.Fanuc/Protocol/FanucJ519Client.cs b/src/Flyshot.Runtime.Fanuc/Protocol/FanucJ519Client.cs
index 7600380..67a91c8 100644
--- a/src/Flyshot.Runtime.Fanuc/Protocol/FanucJ519Client.cs
+++ b/src/Flyshot.Runtime.Fanuc/Protocol/FanucJ519Client.cs
@@ -609,8 +609,8 @@ public sealed class FanucJ519Client : IDisposable
if (_endCommandStreamAfterQueueDrained && command.LastData == 1)
{
// LastData 是命令流的正常结束语义;结束后保留状态接收,但不继续回发保姿包。
- _monitorOnlyAfterLastData = true;
- _motionStarted = false;
+ // _monitorOnlyAfterLastData = true;
+ // _motionStarted = false;
CompleteCommandQueueLocked();
}
else if (willDrainQueue && _commandQueue is { Count: 0 })
diff --git a/tests/Flyshot.Core.Tests/FanucControllerRuntimeLoggingRegistrationTests.cs b/tests/Flyshot.Core.Tests/FanucControllerRuntimeLoggingRegistrationTests.cs
index 357b7a3..16418a5 100644
--- a/tests/Flyshot.Core.Tests/FanucControllerRuntimeLoggingRegistrationTests.cs
+++ b/tests/Flyshot.Core.Tests/FanucControllerRuntimeLoggingRegistrationTests.cs
@@ -1,5 +1,6 @@
using Flyshot.Runtime.Common;
using Flyshot.Runtime.Fanuc;
+using Flyshot.Runtime.Fanuc.Protocol;
using Microsoft.Extensions.DependencyInjection;
using System.Reflection;
@@ -14,7 +15,7 @@ public sealed class FanucControllerRuntimeLoggingRegistrationTests
/// 验证宿主按 IControllerRuntime 解析 FANUC 运行时时,不会因为可选日志参数而退回到无日志实例。
///
[Fact]
- public void ServiceProvider_Resolves_FanucControllerRuntime_WithLogger()
+ public void ServiceProvider_Resolves_FanucControllerRuntime_WithAllClientLoggers()
{
var services = new ServiceCollection();
services.AddLogging();
@@ -28,5 +29,27 @@ public sealed class FanucControllerRuntimeLoggingRegistrationTests
Assert.NotNull(loggerField);
Assert.NotNull(loggerField!.GetValue(concreteRuntime));
+
+ AssertClientLoggerIsInjected(concreteRuntime, "_commandClient");
+ AssertClientLoggerIsInjected(concreteRuntime, "_stateClient");
+ AssertClientLoggerIsInjected(concreteRuntime, "_j519Client");
+ }
+
+ ///
+ /// 断言运行时持有的底层协议客户端已注入对应类型的日志记录器。
+ ///
+ /// 待检查的协议客户端类型。
+ /// 已经通过依赖注入解析出的 FANUC 运行时。
+ /// 运行时内部保存该客户端实例的字段名。
+ private static void AssertClientLoggerIsInjected(FanucControllerRuntime runtime, string clientFieldName)
+ {
+ var clientField = typeof(FanucControllerRuntime).GetField(clientFieldName, BindingFlags.Instance | BindingFlags.NonPublic);
+
+ Assert.NotNull(clientField);
+ var client = Assert.IsType(clientField!.GetValue(runtime));
+ var loggerField = typeof(TClient).GetField("_logger", BindingFlags.Instance | BindingFlags.NonPublic);
+
+ Assert.NotNull(loggerField);
+ Assert.NotNull(loggerField!.GetValue(client));
}
}
diff --git a/tests/Flyshot.Core.Tests/FanucJ519ClientTests.cs b/tests/Flyshot.Core.Tests/FanucJ519ClientTests.cs
index aff1c16..4030eaa 100644
--- a/tests/Flyshot.Core.Tests/FanucJ519ClientTests.cs
+++ b/tests/Flyshot.Core.Tests/FanucJ519ClientTests.cs
@@ -279,6 +279,34 @@ public sealed class FanucJ519ClientTests : IDisposable
Assert.True(client.IsCommandQueueDrainedForTests());
}
+ ///
+ /// 验证机器人持续不可接收命令时,后续装载的命令队列会被完成,避免执行请求永久等待。
+ ///
+ [Fact]
+ public async Task LoadCommandQueue_WhenLatestStatusDoesNotAcceptCommands_CompletesQueueWait()
+ {
+ using var client = new FanucJ519Client();
+ await client.ConnectAsync("127.0.0.1", Port, _cts.Token);
+ var initResult = await _server.ReceiveAsync(_cts.Token);
+
+ client.StartMotion();
+ await SendStatusPacketAsync(initResult.RemoteEndPoint, sequence: 900, status: 0x04);
+ await Task.Delay(100, _cts.Token);
+
+ client.LoadCommandQueue(
+ [
+ new FanucJ519Command(sequence: 0, targetJoints: [1.0, 0.0, 0.0, 0.0, 0.0, 0.0])
+ ]);
+ await SendStatusPacketAsync(initResult.RemoteEndPoint, sequence: 901, status: 0x04);
+
+ await client.WaitForCommandQueueDrainedAsync(_cts.Token).WaitAsync(TimeSpan.FromSeconds(1), _cts.Token);
+ await Assert.ThrowsAsync(
+ () => _server.ReceiveAsync(_cts.Token).AsTask().WaitAsync(TimeSpan.FromMilliseconds(120)));
+
+ Assert.True(client.IsCommandQueueDrainedForTests());
+ await client.StopMotionAsync(_cts.Token);
+ }
+
///
/// 验证启用命令流结束开关后,LastData 结束帧发送完成会进入仅监控状态,不再继续回发保姿命令。
///
@@ -572,13 +600,14 @@ public sealed class FanucJ519ClientTests : IDisposable
private async Task SendStatusPacketAsync(
IPEndPoint clientEndpoint,
uint sequence,
- IReadOnlyList? jointDegrees = null)
+ IReadOnlyList? jointDegrees = null,
+ byte status = 15)
{
var responsePacket = new byte[FanucJ519Protocol.ResponsePacketLength];
BinaryPrimitives.WriteUInt32BigEndian(responsePacket.AsSpan(0x00, 4), 0);
BinaryPrimitives.WriteUInt32BigEndian(responsePacket.AsSpan(0x04, 4), 1);
BinaryPrimitives.WriteUInt32BigEndian(responsePacket.AsSpan(0x08, 4), sequence);
- responsePacket[0x0c] = 15;
+ responsePacket[0x0c] = status;
if (jointDegrees is not null)
{