✨ feat(planning): 添加离线飞拍规划能力
* 固化离线飞拍轨迹加载、保存和规划结果。 * 真机初始化失败时保留模型加载后的离线规划能力。 * 更新状态接口、调试页和回归测试覆盖。
This commit is contained in:
@@ -16,40 +16,6 @@ namespace Flyshot.Core.Tests;
|
||||
/// </summary>
|
||||
public sealed class RuntimeOrchestrationTests
|
||||
{
|
||||
/// <summary>
|
||||
/// 验证 FANUC 最小运行时执行轨迹后会更新状态快照与最终关节位置。
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void FanucControllerRuntime_ExecuteTrajectory_UpdatesSnapshotAndFinalJointPositions()
|
||||
{
|
||||
var runtime = new FanucControllerRuntime();
|
||||
var robot = TestRobotFactory.CreateRobotProfile();
|
||||
runtime.ResetRobot(robot, "FANUC_LR_Mate_200iD");
|
||||
runtime.SetActiveController(sim: true);
|
||||
runtime.Connect("192.168.10.101");
|
||||
runtime.EnableRobot(bufferSize: 2);
|
||||
|
||||
var result = new TrajectoryResult(
|
||||
programName: "demo",
|
||||
method: PlanningMethod.Icsp,
|
||||
isValid: true,
|
||||
duration: TimeSpan.FromSeconds(1.2),
|
||||
shotEvents: Array.Empty<ShotEvent>(),
|
||||
triggerTimeline: Array.Empty<TrajectoryDoEvent>(),
|
||||
artifacts: Array.Empty<TrajectoryArtifact>(),
|
||||
failureReason: null,
|
||||
usedCache: false,
|
||||
originalWaypointCount: 4,
|
||||
plannedWaypointCount: 4);
|
||||
|
||||
runtime.ExecuteTrajectory(result, [1.0, 2.0, 3.0, 4.0, 5.0, 6.0]);
|
||||
|
||||
var snapshot = runtime.GetSnapshot();
|
||||
Assert.Equal("Connected", snapshot.ConnectionState);
|
||||
Assert.False(snapshot.IsInMotion);
|
||||
Assert.Equal([1.0, 2.0, 3.0, 4.0, 5.0, 6.0], snapshot.JointPositions);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 验证真机运行时会把 TCP 10010 状态通道健康度映射为可诊断连接状态。
|
||||
/// </summary>
|
||||
@@ -761,7 +727,6 @@ public sealed class RuntimeOrchestrationTests
|
||||
{
|
||||
var service = TestRobotFactory.CreateCompatService();
|
||||
service.SetUpRobot("FANUC_LR_Mate_200iD");
|
||||
service.SetActiveController(sim: true);
|
||||
service.Connect("192.168.10.101");
|
||||
service.EnableRobot(2);
|
||||
|
||||
@@ -803,7 +768,6 @@ public sealed class RuntimeOrchestrationTests
|
||||
};
|
||||
|
||||
service.SetUpRobot("FANUC_LR_Mate_200iD");
|
||||
service.SetActiveController(sim: false);
|
||||
service.Connect("192.168.10.101");
|
||||
service.EnableRobot(2);
|
||||
runtime.SetSpeedRatio(0.5);
|
||||
@@ -849,7 +813,6 @@ public sealed class RuntimeOrchestrationTests
|
||||
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());
|
||||
@@ -894,7 +857,6 @@ public sealed class RuntimeOrchestrationTests
|
||||
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());
|
||||
@@ -938,7 +900,6 @@ public sealed class RuntimeOrchestrationTests
|
||||
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());
|
||||
@@ -979,7 +940,6 @@ public sealed class RuntimeOrchestrationTests
|
||||
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());
|
||||
@@ -1030,7 +990,6 @@ public sealed class RuntimeOrchestrationTests
|
||||
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());
|
||||
@@ -1081,7 +1040,6 @@ public sealed class RuntimeOrchestrationTests
|
||||
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());
|
||||
@@ -1240,8 +1198,8 @@ public sealed class RuntimeOrchestrationTests
|
||||
$"ActualSend 应按当前 speedRatio 的执行视图导出,不能再用旧 JointDetialTraj 规划时长约束点数。actual={pointRows.Length}, planningRows={planningExportRowsAtServoPeriod}");
|
||||
Assert.Equal(0.0, pointRows[0][0], precision: 6);
|
||||
Assert.Equal(0.008, pointRows[1][0], precision: 6);
|
||||
Assert.Equal(0.008, timingRows[1][2], precision: 6);
|
||||
Assert.Equal(1.0, timingRows[1][3], precision: 6);
|
||||
Assert.Equal(0.004, timingRows[1][2], precision: 6);
|
||||
Assert.Equal(0.5, timingRows[1][3], precision: 6);
|
||||
Assert.Equal(0.5, timingRows[1][4], precision: 6);
|
||||
Assert.Equal(0.0, timingRows[1][5], precision: 6);
|
||||
Assert.Contains("\"trigger_window_seconds\": 0.1", shotEventsJson);
|
||||
@@ -1371,6 +1329,142 @@ public sealed class RuntimeOrchestrationTests
|
||||
return configRoot;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 验证未加载机器人模型时仍可上传、列出和删除飞拍资产。
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void ControllerClientCompatService_ManagesFlyshotAssetsWithoutRobotSetup()
|
||||
{
|
||||
var configRoot = CreateTempConfigRoot();
|
||||
try
|
||||
{
|
||||
var options = new ControllerClientCompatOptions { ConfigRoot = configRoot };
|
||||
var service = new ControllerClientCompatService(
|
||||
options,
|
||||
new ControllerClientCompatRobotCatalog(options, new RobotModelLoader()),
|
||||
new RecordingControllerRuntime(),
|
||||
new ControllerClientTrajectoryOrchestrator(),
|
||||
new RobotConfigLoader());
|
||||
|
||||
service.UploadTrajectory(TestRobotFactory.CreateUploadedTrajectoryWithSingleShot());
|
||||
|
||||
Assert.Contains("demo-flyshot", service.ListTrajectoryNames());
|
||||
Assert.Contains(
|
||||
"\"demo-flyshot\"",
|
||||
File.ReadAllText(Path.Combine(configRoot, "RobotConfig.json")),
|
||||
StringComparison.Ordinal);
|
||||
|
||||
service.DeleteTrajectory("demo-flyshot");
|
||||
|
||||
Assert.DoesNotContain("demo-flyshot", service.ListTrajectoryNames());
|
||||
}
|
||||
finally
|
||||
{
|
||||
Directory.Delete(configRoot, recursive: true);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 验证重新加载机器人模型不会清空已经上传的飞拍资产。
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void ControllerClientCompatService_SetUpRobot_PreservesUploadedFlyshotAssets()
|
||||
{
|
||||
var configRoot = CreateTempConfigRoot();
|
||||
try
|
||||
{
|
||||
var options = new ControllerClientCompatOptions { ConfigRoot = configRoot };
|
||||
var service = new ControllerClientCompatService(
|
||||
options,
|
||||
new ControllerClientCompatRobotCatalog(options, new RobotModelLoader()),
|
||||
new RecordingControllerRuntime(),
|
||||
new ControllerClientTrajectoryOrchestrator(),
|
||||
new RobotConfigLoader());
|
||||
|
||||
service.UploadTrajectory(TestRobotFactory.CreateUploadedTrajectoryWithSingleShot());
|
||||
service.SetUpRobot("FANUC_LR_Mate_200iD");
|
||||
|
||||
Assert.Contains("demo-flyshot", service.ListTrajectoryNames());
|
||||
Assert.Equal("OfflineReady", service.GetStatusSnapshotMetadata().OperatingMode);
|
||||
Assert.True(service.GetStatusSnapshotMetadata().CanPlanOffline);
|
||||
Assert.False(service.GetStatusSnapshotMetadata().CanExecuteRealMotion);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Directory.Delete(configRoot, recursive: true);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 验证离线状态可以生成完整最终发送点列,并返回导出文件绝对路径。
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void ControllerClientCompatService_SaveTrajectoryInfo_ReturnsOfflinePlanningResult()
|
||||
{
|
||||
var configRoot = CreateTempConfigRoot();
|
||||
WriteRobotConfigWithDemoTrajectory(configRoot, planningSpeedScale: 0.45, speedRatio: 1.0);
|
||||
try
|
||||
{
|
||||
var options = new ControllerClientCompatOptions { ConfigRoot = configRoot };
|
||||
var service = new ControllerClientCompatService(
|
||||
options,
|
||||
new ControllerClientCompatRobotCatalog(options, new RobotModelLoader()),
|
||||
new RecordingControllerRuntime(),
|
||||
new ControllerClientTrajectoryOrchestrator(),
|
||||
new RobotConfigLoader());
|
||||
|
||||
service.SetUpRobot("FANUC_LR_Mate_200iD");
|
||||
var result = service.SaveTrajectoryInfo("demo-flyshot");
|
||||
|
||||
Assert.True(result.IsValid);
|
||||
Assert.Equal("demo-flyshot", result.TrajectoryName);
|
||||
Assert.Equal(1.0, result.SpeedRatio, precision: 6);
|
||||
Assert.NotEmpty(result.FinalSendPoints);
|
||||
Assert.NotEmpty(result.ShotEvents);
|
||||
Assert.NotEmpty(result.CartesianTrajectory);
|
||||
Assert.True(result.Diagnostics.MaxAbsVelocityDegreesPerSecond > 0.0);
|
||||
Assert.All(result.ArtifactPaths.Values, path => Assert.True(Path.IsPathFullyQualified(path)));
|
||||
}
|
||||
finally
|
||||
{
|
||||
Directory.Delete(configRoot, recursive: true);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 验证模型加载成功但真机连接失败时,状态降级为 OfflineReady 并保留离线规划能力。
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void ControllerClientCompatService_InitializeRobot_ConnectionFailureKeepsOfflineReady()
|
||||
{
|
||||
var configRoot = CreateTempConfigRoot();
|
||||
try
|
||||
{
|
||||
var options = new ControllerClientCompatOptions { ConfigRoot = configRoot };
|
||||
var runtime = new RecordingControllerRuntime { ThrowOnConnect = true };
|
||||
var service = new ControllerClientCompatService(
|
||||
options,
|
||||
new ControllerClientCompatRobotCatalog(options, new RobotModelLoader()),
|
||||
runtime,
|
||||
new ControllerClientTrajectoryOrchestrator(),
|
||||
new RobotConfigLoader());
|
||||
|
||||
Assert.Throws<InvalidOperationException>(
|
||||
() => service.InitializeRobot("FANUC_LR_Mate_200iD", "192.168.10.101"));
|
||||
|
||||
var metadata = service.GetStatusSnapshotMetadata();
|
||||
Assert.Equal("OfflineReady", metadata.OperatingMode);
|
||||
Assert.True(metadata.CanPlanOffline);
|
||||
Assert.False(metadata.CanExecuteRealMotion);
|
||||
Assert.NotNull(metadata.LastConnectionError);
|
||||
Assert.NotNull(metadata.LastConnectionErrorAt);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Directory.Delete(configRoot, recursive: true);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 写入包含一条飞拍轨迹的最小 RobotConfig.json,供兼容服务从统一配置恢复轨迹。
|
||||
/// </summary>
|
||||
@@ -1952,7 +2046,7 @@ internal static class TestRobotFactory
|
||||
return new ControllerClientCompatService(
|
||||
options,
|
||||
new ControllerClientCompatRobotCatalog(options, new RobotModelLoader()),
|
||||
new FanucControllerRuntime(),
|
||||
new RecordingControllerRuntime(),
|
||||
new ControllerClientTrajectoryOrchestrator(),
|
||||
new RobotConfigLoader());
|
||||
}
|
||||
@@ -2024,6 +2118,11 @@ internal sealed class RecordingControllerRuntime : IControllerRuntime
|
||||
/// </summary>
|
||||
public TrajectoryResult? LastExecutedResult { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置连接时是否抛出测试异常。
|
||||
/// </summary>
|
||||
public bool ThrowOnConnect { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public void ResetRobot(RobotProfile robot, string robotName)
|
||||
{
|
||||
@@ -2038,6 +2137,10 @@ internal sealed class RecordingControllerRuntime : IControllerRuntime
|
||||
/// <inheritdoc />
|
||||
public void Connect(string robotIp)
|
||||
{
|
||||
if (ThrowOnConnect)
|
||||
{
|
||||
throw new InvalidOperationException("测试连接失败。");
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
Reference in New Issue
Block a user