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) {