Files
FlyShotHost/docs/superpowers/plans/2026-04-24-minimal-runtime-orchestration.md
yunxiao.zhu 8a20d9f507 feat: 实现 ControllerClient HTTP 兼容层及 FANUC 运行时
- 新增 Flyshot.ControllerClientCompat 兼容层模块
  - 新增 Flyshot.Runtime.Fanuc 运行时模块
  - 新增 LegacyHttpApiController 暴露 HTTP 兼容 API
  - 补充 RuntimeOrchestrationTests 等测试覆盖
  - 补充 docs/ 兼容性需求与逆向工程文档
  - 更新 Host 注册、配置及解决方案引用

  变更概览:
  - Flyshot.ControllerClientCompat — 旧 ControllerClient 语义的 HTTP 适配
  - Flyshot.Runtime.Fanuc — IControllerRuntime 的 FANUC 真机实现
  - LegacyHttpApiController — HTTP API 兼容旧 SDK
  - docs/ — 兼容性需求与逆向工程分析文档
  - 测试:RuntimeOrchestrationTests、LegacyHttpApiCompatibilityTests
2026-04-24 16:55:25 +08:00

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
- [ ] 落地真实 10010 / 10012 / 60015 通讯
- [ ] 落地 Web 状态页
  • Step 2: Run final build

Run: dotnet build FlyshotReplacement.sln --no-restore -v minimal -m:1 -nodeReuse:false Expected: PASS with 0 errors.