* 在 FanucCommandProtocol/Client 中补齐速度倍率、TCP 位姿和 IO 的封包/解析,并引入 FanucIoTypes 字符串到枚举映射 * FanucControllerRuntime 在非仿真模式下接入真机命令通道,本地 缓存仅作为兜底,TCP 操作扩展为 7 维 Pose * FanucStateClient 增加帧超时检测、退避自动重连和诊断状态接口, 超时或重连期间不再把陈旧帧当作当前机器人状态 * FanucStateProtocol 锁定 90B 帧字段为 pose[6]、joint[6]、 external_axes[3] 和 raw_tail_words[4],并保留状态字诊断槽位 * ICspPlanner 增加 global_scale > 1.0 失败判定,self-adapt-icsp 内部禁用该判定以保留补点重试链路 * 同步更新 README/AGENTS/计划文档的 todo 状态和实现说明
273 lines
11 KiB
Markdown
273 lines
11 KiB
Markdown
# 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`
|
|
|
|
- [x] **Step 1: Write the failing runtime test**
|
|
|
|
```csharp
|
|
[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);
|
|
}
|
|
```
|
|
|
|
- [x] **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.
|
|
|
|
- [x] **Step 3: Write the minimal runtime contracts and implementation**
|
|
|
|
```csharp
|
|
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);
|
|
}
|
|
```
|
|
|
|
```csharp
|
|
public sealed class FanucControllerRuntime : IControllerRuntime
|
|
{
|
|
// Stage-1 runtime: owns controller state in one place so later sockets can replace internals without rewriting compat service.
|
|
}
|
|
```
|
|
|
|
- [x] **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`
|
|
|
|
- [x] **Step 1: Write the failing orchestration tests**
|
|
|
|
```csharp
|
|
[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);
|
|
}
|
|
```
|
|
|
|
- [x] **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.
|
|
|
|
- [x] **Step 3: Write the minimal orchestration layer**
|
|
|
|
```csharp
|
|
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; }
|
|
}
|
|
```
|
|
|
|
```csharp
|
|
public sealed class ControllerClientTrajectoryOrchestrator
|
|
{
|
|
public PlannedExecutionBundle PlanOrdinaryTrajectory(RobotProfile robot, IReadOnlyList<IReadOnlyList<double>> waypoints) { ... }
|
|
public PlannedExecutionBundle PlanUploadedFlyshot(RobotProfile robot, ControllerClientCompatUploadedTrajectory uploaded) { ... }
|
|
}
|
|
```
|
|
|
|
- [x] **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`
|
|
|
|
- [x] **Step 1: Write the failing compat-service test**
|
|
|
|
```csharp
|
|
[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]
|
|
]));
|
|
}
|
|
```
|
|
|
|
- [x] **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".
|
|
|
|
- [x] **Step 3: Rewire service to the runtime and orchestrator**
|
|
|
|
```csharp
|
|
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);
|
|
}
|
|
}
|
|
```
|
|
|
|
- [x] **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.
|
|
|
|
- [x] **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`
|
|
|
|
- [x] **Step 1: Update docs to reflect the new stage**
|
|
|
|
```markdown
|
|
- [x] 落地最小 FANUC 运行时骨架
|
|
- [x] 将 ExecuteTrajectory / ExecuteFlyShotTraj 接入 Planning + Triggering + Runtime
|
|
- [x] 落地 Web 状态页
|
|
- [x] 固化 10010 / 10012 / 60015 FANUC 基础协议帧编解码
|
|
- [ ] 落地真实 10010 / 10012 / 60015 Socket 通讯与现场联调
|
|
```
|
|
|
|
- [x] **Step 2: Run final build**
|
|
|
|
Run: `dotnet build FlyshotReplacement.sln --no-restore -v minimal -m:1 -nodeReuse:false`
|
|
Expected: PASS with 0 errors.
|