using Flyshot.ControllerClientCompat; using Flyshot.Core.Config; using Flyshot.Core.Domain; using Flyshot.Runtime.Fanuc; namespace Flyshot.Core.Tests; /// /// 验证最小运行时编排链路会把规划结果交给控制器运行时,而不是停留在兼容层内存状态。 /// public sealed class RuntimeOrchestrationTests { /// /// 验证 FANUC 最小运行时执行轨迹后会更新状态快照与最终关节位置。 /// [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(), triggerTimeline: Array.Empty(), artifacts: Array.Empty(), 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); } /// /// 验证普通轨迹会先进入 ICSP 规划,并沿用 ICSP 对示教点数量的约束。 /// [Fact] public void ControllerClientTrajectoryOrchestrator_PlanOrdinaryTrajectory_RejectsThreeTeachPoints() { var orchestrator = new ControllerClientTrajectoryOrchestrator(); var robot = TestRobotFactory.CreateRobotProfile(); void Act() => orchestrator.PlanOrdinaryTrajectory( robot, [ [0.0, 0.0, 0.0, 0.0, 0.0, 0.0], [0.5, 0.0, 0.0, 0.0, 0.0, 0.0], [1.0, 0.0, 0.0, 0.0, 0.0, 0.0] ]); Assert.Throws(Act); } /// /// 验证已上传飞拍轨迹会经过 self-adapt-icsp 并生成拍照触发时间轴。 /// [Fact] public void ControllerClientTrajectoryOrchestrator_PlanUploadedFlyshot_BuildsShotTimeline() { var orchestrator = new ControllerClientTrajectoryOrchestrator(); var robot = TestRobotFactory.CreateRobotProfile(); var uploaded = TestRobotFactory.CreateUploadedTrajectoryWithSingleShot(); var bundle = orchestrator.PlanUploadedFlyshot(robot, uploaded); Assert.True(bundle.Result.IsValid); Assert.Single(bundle.Result.ShotEvents); Assert.Single(bundle.Result.TriggerTimeline); } /// /// 验证兼容服务执行普通轨迹时会进入规划链路,而不是直接把最后一个路点写入状态。 /// [Fact] public void ControllerClientCompatService_ExecuteTrajectory_RejectsThreeTeachPointsAfterPlanningIsIntroduced() { var service = TestRobotFactory.CreateCompatService(); service.SetUpRobot("FANUC_LR_Mate_200iD"); service.SetActiveController(sim: true); service.Connect("192.168.10.101"); service.EnableRobot(2); void Act() => service.ExecuteTrajectory( [ [0.0, 0.0, 0.0, 0.0, 0.0, 0.0], [0.5, 0.0, 0.0, 0.0, 0.0, 0.0], [1.0, 0.0, 0.0, 0.0, 0.0, 0.0] ]); Assert.Throws(Act); } } /// /// 为运行时编排测试构造稳定的最小领域对象。 /// internal static class TestRobotFactory { /// /// 构造六轴测试机器人配置,避免运行时测试依赖真实 .robot 文件。 /// /// 可用于规划和运行时状态校验的机器人配置。 public static RobotProfile CreateRobotProfile() { return new RobotProfile( name: "TestRobot", modelPath: "Models/Test.robot", degreesOfFreedom: 6, jointLimits: Enumerable.Range(1, 6) .Select(static index => new JointLimit($"J{index}", 10.0, 20.0, 100.0)) .ToArray(), jointCouplings: Array.Empty(), servoPeriod: TimeSpan.FromMilliseconds(8), triggerPeriod: TimeSpan.FromMilliseconds(8)); } /// /// 构造一条含单个拍照点的上传飞拍轨迹。 /// /// 可用于触发时间轴测试的上传轨迹。 public static ControllerClientCompatUploadedTrajectory CreateUploadedTrajectoryWithSingleShot() { return new ControllerClientCompatUploadedTrajectory( name: "demo-flyshot", waypoints: [ [0.0, 0.0, 0.0, 0.0, 0.0, 0.0], [0.1, 0.0, 0.0, 0.0, 0.0, 0.0], [0.2, 0.0, 0.0, 0.0, 0.0, 0.0], [0.3, 0.0, 0.0, 0.0, 0.0, 0.0] ], shotFlags: [false, true, false, false], offsetValues: [0, 1, 0, 0], addressGroups: [ Array.Empty(), [7, 8], Array.Empty(), Array.Empty() ]); } /// /// 构造一份真实依赖注入等价的兼容服务,覆盖运行时和编排器协作。 /// /// 可执行 ControllerClient 兼容语义的服务实例。 public static ControllerClientCompatService CreateCompatService() { var options = new ControllerClientCompatOptions { WorkspaceRoot = GetWorkspaceRoot() }; return new ControllerClientCompatService( options, new ControllerClientCompatRobotCatalog(options, new RobotModelLoader()), new FanucControllerRuntime(), new ControllerClientTrajectoryOrchestrator()); } /// /// 定位父工作区根目录,供兼容服务加载真实机器人模型。 /// /// 父工作区根目录。 private static string GetWorkspaceRoot() { var current = new DirectoryInfo(AppContext.BaseDirectory); while (current is not null) { if (File.Exists(Path.Combine(current.FullName, "FlyshotReplacement.sln"))) { return Path.GetFullPath(Path.Combine(current.FullName, "..")); } current = current.Parent; } throw new DirectoryNotFoundException("Unable to locate the flyshot workspace root."); } }