✨ feat(compat): 补齐飞拍执行等待与 FANUC 状态驱动链路
- 为 ExecuteFlyShotTraj 补齐 wait 语义,并让 move_to_start 先完成临时 PTP 运动后再启动正式飞拍轨迹 - 将 J519 命令发送改为由机器人 UDP status sequence 驱动, 避免在未收到状态包时主动发周期命令 - 将 10010 状态通道关节字段统一按 JointRadians 命名, 同步更新运行时读取逻辑与协议测试 - 新增 FANUC 10010 状态帧、流运动手册和 Python client 逆向文档,并更新 README 与兼容需求说明 - 补充兼容层编排测试与 HTTP 集成测试,覆盖 wait 和 move_to_start 串行化行为
This commit is contained in:
@@ -293,6 +293,92 @@ public sealed class RuntimeOrchestrationTests
|
||||
Assert.Throws<ArgumentException>(Act);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 验证 ExecuteFlyShotTraj(move_to_start=true) 会先执行稠密 PTP 到起点,并等待该段运动完成后再启动飞拍轨迹。
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void ControllerClientCompatService_ExecuteTrajectoryByName_MoveToStartWaitsBeforeFlyshot()
|
||||
{
|
||||
var configRoot = CreateTempConfigRoot();
|
||||
try
|
||||
{
|
||||
var options = new ControllerClientCompatOptions
|
||||
{
|
||||
ConfigRoot = configRoot
|
||||
};
|
||||
var runtime = new DelayedCompletionControllerRuntime(
|
||||
initialJointPositions: [0.4, 0.0, 0.0, 0.0, 0.0, 0.0],
|
||||
firstMotionCompletionDelay: TimeSpan.FromMilliseconds(80));
|
||||
var service = new ControllerClientCompatService(
|
||||
options,
|
||||
new ControllerClientCompatRobotCatalog(options, new RobotModelLoader()),
|
||||
runtime,
|
||||
new ControllerClientTrajectoryOrchestrator(),
|
||||
new RobotConfigLoader());
|
||||
|
||||
service.SetUpRobot("FANUC_LR_Mate_200iD");
|
||||
service.SetActiveController(sim: false);
|
||||
service.Connect("192.168.10.101");
|
||||
service.EnableRobot(2);
|
||||
service.UploadTrajectory(TestRobotFactory.CreateUploadedTrajectoryWithSingleShot());
|
||||
|
||||
service.ExecuteTrajectoryByName(
|
||||
"demo-flyshot",
|
||||
new FlyshotExecutionOptions(moveToStart: true, method: "icsp", saveTrajectory: false, useCache: false));
|
||||
|
||||
Assert.True(runtime.ExecuteCalls.Count >= 2);
|
||||
Assert.NotNull(runtime.ExecuteCalls[0].Result.DenseJointTrajectory);
|
||||
Assert.True(runtime.ExecuteCalls[0].Result.DenseJointTrajectory!.Count > 1);
|
||||
Assert.False(runtime.SecondTrajectoryStartedBeforeFirstMotionCompleted);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Directory.Delete(configRoot, recursive: true);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 验证 ExecuteFlyShotTraj(wait=true) 会等待正式飞拍轨迹完成后再返回。
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void ControllerClientCompatService_ExecuteTrajectoryByName_WaitTrueWaitsForFlyshotCompletion()
|
||||
{
|
||||
var configRoot = CreateTempConfigRoot();
|
||||
try
|
||||
{
|
||||
var options = new ControllerClientCompatOptions
|
||||
{
|
||||
ConfigRoot = configRoot
|
||||
};
|
||||
var runtime = new DelayedCompletionControllerRuntime(
|
||||
initialJointPositions: [0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
|
||||
firstMotionCompletionDelay: TimeSpan.FromMilliseconds(80));
|
||||
var service = new ControllerClientCompatService(
|
||||
options,
|
||||
new ControllerClientCompatRobotCatalog(options, new RobotModelLoader()),
|
||||
runtime,
|
||||
new ControllerClientTrajectoryOrchestrator(),
|
||||
new RobotConfigLoader());
|
||||
|
||||
service.SetUpRobot("FANUC_LR_Mate_200iD");
|
||||
service.SetActiveController(sim: false);
|
||||
service.Connect("192.168.10.101");
|
||||
service.EnableRobot(2);
|
||||
service.UploadTrajectory(TestRobotFactory.CreateUploadedTrajectoryWithSingleShot());
|
||||
|
||||
service.ExecuteTrajectoryByName(
|
||||
"demo-flyshot",
|
||||
new FlyshotExecutionOptions(moveToStart: false, method: "icsp", saveTrajectory: false, useCache: false, wait: true));
|
||||
|
||||
Assert.Single(runtime.ExecuteCalls);
|
||||
Assert.False(runtime.GetSnapshot().IsInMotion);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Directory.Delete(configRoot, recursive: true);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 验证兼容服务初始化机器人时会把 RobotConfig.json 中的 acc_limit / jerk_limit 传给模型加载器。
|
||||
/// </summary>
|
||||
@@ -650,3 +736,164 @@ internal sealed class RecordingControllerRuntime : IControllerRuntime
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 模拟第一段运动异步完成的测试运行时,用于验证兼容层是否等待 move_to_start 完成。
|
||||
/// </summary>
|
||||
internal sealed class DelayedCompletionControllerRuntime : IControllerRuntime
|
||||
{
|
||||
private readonly object _lock = new();
|
||||
private readonly TimeSpan _firstMotionCompletionDelay;
|
||||
private double[] _jointPositions;
|
||||
private bool _isEnabled;
|
||||
private bool _isInMotion;
|
||||
private bool _firstMotionCompleted;
|
||||
|
||||
/// <summary>
|
||||
/// 初始化可延迟完成第一段运动的测试运行时。
|
||||
/// </summary>
|
||||
/// <param name="initialJointPositions">运行时报告的初始关节位置。</param>
|
||||
/// <param name="firstMotionCompletionDelay">第一段运动完成前保持忙碌的时间。</param>
|
||||
public DelayedCompletionControllerRuntime(
|
||||
IReadOnlyList<double> initialJointPositions,
|
||||
TimeSpan firstMotionCompletionDelay)
|
||||
{
|
||||
_jointPositions = initialJointPositions.ToArray();
|
||||
_firstMotionCompletionDelay = firstMotionCompletionDelay;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取所有 ExecuteTrajectory 调用记录。
|
||||
/// </summary>
|
||||
public List<(TrajectoryResult Result, IReadOnlyList<double> FinalJointPositions)> ExecuteCalls { get; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// 获取第二条轨迹是否在第一段 move_to_start 完成前启动。
|
||||
/// </summary>
|
||||
public bool SecondTrajectoryStartedBeforeFirstMotionCompleted { get; private set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public void ResetRobot(RobotProfile robot, string robotName)
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void SetActiveController(bool sim)
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Connect(string robotIp)
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Disconnect()
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void EnableRobot(int bufferSize)
|
||||
{
|
||||
_isEnabled = true;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void DisableRobot()
|
||||
{
|
||||
_isEnabled = false;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void StopMove()
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
_isInMotion = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public double GetSpeedRatio() => 1.0;
|
||||
|
||||
/// <inheritdoc />
|
||||
public void SetSpeedRatio(double ratio)
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IReadOnlyList<double> GetTcp() => [0.0, 0.0, 0.0];
|
||||
|
||||
/// <inheritdoc />
|
||||
public void SetTcp(double x, double y, double z)
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool GetIo(int port, string ioType) => false;
|
||||
|
||||
/// <inheritdoc />
|
||||
public void SetIo(int port, bool value, string ioType)
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IReadOnlyList<double> GetJointPositions()
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
return _jointPositions.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IReadOnlyList<double> GetPose() => Array.Empty<double>();
|
||||
|
||||
/// <inheritdoc />
|
||||
public ControllerStateSnapshot GetSnapshot()
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
return new ControllerStateSnapshot(
|
||||
capturedAt: DateTimeOffset.UtcNow,
|
||||
connectionState: "Connected",
|
||||
isEnabled: _isEnabled,
|
||||
isInMotion: _isInMotion,
|
||||
speedRatio: 1.0,
|
||||
jointPositions: _jointPositions.ToArray(),
|
||||
cartesianPose: Array.Empty<double>(),
|
||||
activeAlarms: Array.Empty<RuntimeAlarm>());
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void ExecuteTrajectory(TrajectoryResult result, IReadOnlyList<double> finalJointPositions)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
ExecuteCalls.Add((result, finalJointPositions.ToArray()));
|
||||
if (ExecuteCalls.Count == 1)
|
||||
{
|
||||
_isInMotion = true;
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
await Task.Delay(_firstMotionCompletionDelay).ConfigureAwait(false);
|
||||
lock (_lock)
|
||||
{
|
||||
_jointPositions = finalJointPositions.ToArray();
|
||||
_isInMotion = false;
|
||||
_firstMotionCompleted = true;
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_firstMotionCompleted)
|
||||
{
|
||||
SecondTrajectoryStartedBeforeFirstMotionCompleted = true;
|
||||
}
|
||||
|
||||
_jointPositions = finalJointPositions.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user