* 扩展 ControllerClient 兼容层的执行参数和运行时编排 * 新增 /status 页面与 /api/status/snapshot 状态快照接口 * 补充 FANUC 协议、客户端和状态接口的最小验证测试 * 更新 README、兼容要求和真机 Socket 通信实现计划
11 KiB
Minimal Runtime Orchestration Implementation Plan
For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (
- [ ]) syntax for tracking.
Goal: Build the first real execution slice after the HTTP-only refactor by routing ExecuteTrajectory and ExecuteFlyShotTraj through planning, triggering, and a new minimal FANUC runtime project instead of in-memory last-point assignment.
Architecture: Keep Flyshot.Server.Host as a pure HTTP adapter and keep uploaded program state in Flyshot.ControllerClientCompat, but move controller runtime state into a new Flyshot.Runtime.Fanuc project and add a focused planning/orchestration helper in Flyshot.ControllerClientCompat. Ordinary trajectory execution will use ICspPlanner; uploaded flyshot execution will use SelfAdaptIcspPlanner plus ShotTimelineBuilder, then hand the resulting TrajectoryResult to the runtime.
Tech Stack: C#, .NET 8, xUnit, existing Flyshot.Core.Domain, Flyshot.Core.Planning, Flyshot.Core.Triggering, ASP.NET Core DI.
Task 1: Add Runtime Contracts And Minimal FANUC Runtime
Files:
-
Create:
src/Flyshot.Runtime.Common/IControllerRuntime.cs -
Create:
src/Flyshot.Runtime.Fanuc/Flyshot.Runtime.Fanuc.csproj -
Create:
src/Flyshot.Runtime.Fanuc/FanucControllerRuntime.cs -
Modify:
FlyshotReplacement.sln -
Modify:
src/Flyshot.Server.Host/Flyshot.Server.Host.csproj -
Modify:
src/Flyshot.Server.Host/Program.cs -
Test:
tests/Flyshot.Core.Tests/RuntimeOrchestrationTests.cs -
Step 1: Write the failing runtime test
[Fact]
public void FanucControllerRuntime_ExecuteTrajectory_UpdatesSnapshotAndFinalJointPositions()
{
var runtime = new FanucControllerRuntime();
var robot = TestRobotFactory.CreateRobotProfile();
runtime.ResetRobot(robot, "FANUC_LR_Mate_200iD");
runtime.SetActiveController(sim: false);
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);
}
- Step 2: Run test to verify it fails
Run: dotnet test tests/Flyshot.Core.Tests/Flyshot.Core.Tests.csproj --filter FanucControllerRuntime_ExecuteTrajectory_UpdatesSnapshotAndFinalJointPositions -v minimal -m:1 -nodeReuse:false
Expected: FAIL because FanucControllerRuntime and IControllerRuntime do not exist.
- Step 3: Write the minimal runtime contracts and implementation
public interface IControllerRuntime
{
void ResetRobot(RobotProfile robot, string robotName);
void SetActiveController(bool sim);
void Connect(string robotIp);
void Disconnect();
void EnableRobot(int bufferSize);
void DisableRobot();
void StopMove();
double GetSpeedRatio();
void SetSpeedRatio(double ratio);
IReadOnlyList<double> GetTcp();
void SetTcp(double x, double y, double z);
bool GetIo(int port, string ioType);
void SetIo(int port, bool value, string ioType);
IReadOnlyList<double> GetJointPositions();
IReadOnlyList<double> GetPose();
ControllerStateSnapshot GetSnapshot();
void ExecuteTrajectory(TrajectoryResult result, IReadOnlyList<double> finalJointPositions);
}
public sealed class FanucControllerRuntime : IControllerRuntime
{
// Stage-1 runtime: owns controller state in one place so later sockets can replace internals without rewriting compat service.
}
- Step 4: Run test to verify it passes
Run: dotnet test tests/Flyshot.Core.Tests/Flyshot.Core.Tests.csproj --filter FanucControllerRuntime_ExecuteTrajectory_UpdatesSnapshotAndFinalJointPositions -v minimal -m:1 -nodeReuse:false
Expected: PASS.
Task 2: Add Planning And Triggering Orchestration For Execution
Files:
-
Create:
src/Flyshot.ControllerClientCompat/PlannedExecutionBundle.cs -
Create:
src/Flyshot.ControllerClientCompat/ControllerClientTrajectoryOrchestrator.cs -
Modify:
src/Flyshot.ControllerClientCompat/Flyshot.ControllerClientCompat.csproj -
Modify:
tests/Flyshot.Core.Tests/Flyshot.Core.Tests.csproj -
Test:
tests/Flyshot.Core.Tests/RuntimeOrchestrationTests.cs -
Step 1: Write the failing orchestration tests
[Fact]
public void ControllerClientTrajectoryOrchestrator_PlanOrdinaryTrajectory_RejectsThreeTeachPoints()
{
var orchestrator = new ControllerClientTrajectoryOrchestrator();
var robot = TestRobotFactory.CreateRobotProfile();
Assert.Throws<ArgumentException>(() =>
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]
]));
}
[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);
}
- Step 2: Run tests to verify they fail
Run: dotnet test tests/Flyshot.Core.Tests/Flyshot.Core.Tests.csproj --filter ControllerClientTrajectoryOrchestrator -v minimal -m:1 -nodeReuse:false
Expected: FAIL because the orchestrator types do not exist.
- Step 3: Write the minimal orchestration layer
public sealed class PlannedExecutionBundle
{
public PlannedExecutionBundle(PlannedTrajectory plannedTrajectory, ShotTimeline shotTimeline, TrajectoryResult result)
{
PlannedTrajectory = plannedTrajectory;
ShotTimeline = shotTimeline;
Result = result;
}
public PlannedTrajectory PlannedTrajectory { get; }
public ShotTimeline ShotTimeline { get; }
public TrajectoryResult Result { get; }
}
public sealed class ControllerClientTrajectoryOrchestrator
{
public PlannedExecutionBundle PlanOrdinaryTrajectory(RobotProfile robot, IReadOnlyList<IReadOnlyList<double>> waypoints) { ... }
public PlannedExecutionBundle PlanUploadedFlyshot(RobotProfile robot, ControllerClientCompatUploadedTrajectory uploaded) { ... }
}
- Step 4: Run tests to verify they pass
Run: dotnet test tests/Flyshot.Core.Tests/Flyshot.Core.Tests.csproj --filter ControllerClientTrajectoryOrchestrator -v minimal -m:1 -nodeReuse:false
Expected: PASS.
Task 3: Rewire ControllerClientCompatService To Runtime + Orchestrator
Files:
-
Modify:
src/Flyshot.ControllerClientCompat/ControllerClientCompatService.cs -
Modify:
src/Flyshot.ControllerClientCompat/ControllerClientCompatServiceCollectionExtensions.cs -
Modify:
src/Flyshot.ControllerClientCompat/IControllerClientCompatService.cs -
Modify:
tests/Flyshot.Server.IntegrationTests/LegacyHttpApiCompatibilityTests.cs -
Modify:
tests/Flyshot.Server.IntegrationTests/ControllerClientCompatRegistrationTests.cs -
Test:
tests/Flyshot.Core.Tests/RuntimeOrchestrationTests.cs -
Test:
tests/Flyshot.Server.IntegrationTests/LegacyHttpApiCompatibilityTests.cs -
Step 1: Write the failing compat-service test
[Fact]
public void ControllerClientCompatService_ExecuteTrajectory_RejectsThreeTeachPointsAfterPlanningIsIntroduced()
{
var service = TestRobotFactory.CreateCompatService();
service.SetUpRobot("FANUC_LR_Mate_200iD");
service.SetActiveController(sim: false);
service.Connect("192.168.10.101");
service.EnableRobot(2);
Assert.Throws<ArgumentException>(() =>
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]
]));
}
- Step 2: Run test to verify it fails
Run: dotnet test tests/Flyshot.Core.Tests/Flyshot.Core.Tests.csproj --filter ControllerClientCompatService_ExecuteTrajectory_RejectsThreeTeachPointsAfterPlanningIsIntroduced -v minimal -m:1 -nodeReuse:false
Expected: FAIL because current service still treats ordinary execution as "move to last waypoint".
- Step 3: Rewire service to the runtime and orchestrator
public sealed class ControllerClientCompatService : IControllerClientCompatService
{
private readonly IControllerRuntime _runtime;
private readonly ControllerClientTrajectoryOrchestrator _trajectoryOrchestrator;
public void ExecuteTrajectory(IReadOnlyList<IReadOnlyList<double>> waypoints)
{
var robot = RequireActiveRobot();
var bundle = _trajectoryOrchestrator.PlanOrdinaryTrajectory(robot, waypoints);
_runtime.ExecuteTrajectory(bundle.Result, bundle.PlannedTrajectory.PlannedWaypoints[^1].Positions);
}
public void ExecuteTrajectoryByName(string name)
{
var robot = RequireActiveRobot();
var uploaded = RequireUploadedTrajectory(name);
var bundle = _trajectoryOrchestrator.PlanUploadedFlyshot(robot, uploaded);
_runtime.ExecuteTrajectory(bundle.Result, bundle.PlannedTrajectory.PlannedWaypoints[^1].Positions);
}
}
- Step 4: Run focused tests to verify green
Run: dotnet test tests/Flyshot.Core.Tests/Flyshot.Core.Tests.csproj --filter "ControllerClientCompatService|ControllerClientTrajectoryOrchestrator|FanucControllerRuntime" -v minimal -m:1 -nodeReuse:false
Expected: PASS.
- Step 5: Run integration verification
Run: dotnet test tests/Flyshot.Server.IntegrationTests/Flyshot.Server.IntegrationTests.csproj -v minimal -m:1 -nodeReuse:false
Expected: PASS, with existing HTTP compatibility tests still green.
Task 4: Verify Solution Build And Update Progress Docs
Files:
-
Modify:
README.md -
Modify:
AGENTS.md -
Modify:
docs/controller-client-api-compatibility-requirements.md -
Step 1: Update docs to reflect the new stage
- [x] 落地最小 FANUC 运行时骨架
- [x] 将 ExecuteTrajectory / ExecuteFlyShotTraj 接入 Planning + Triggering + Runtime
- [x] 落地 Web 状态页
- [x] 固化 10010 / 10012 / 60015 FANUC 基础协议帧编解码
- [ ] 落地真实 10010 / 10012 / 60015 Socket 通讯与现场联调
- Step 2: Run final build
Run: dotnet build FlyshotReplacement.sln --no-restore -v minimal -m:1 -nodeReuse:false
Expected: PASS with 0 errors.