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
This commit is contained in:
2026-04-24 16:55:25 +08:00
parent 4eeaa3fef3
commit 8a20d9f507
35 changed files with 3869 additions and 10 deletions

View File

@@ -0,0 +1,126 @@
using Flyshot.Core.Domain;
using Flyshot.Core.Planning;
using Flyshot.Core.Triggering;
namespace Flyshot.ControllerClientCompat;
/// <summary>
/// 负责把 ControllerClient 兼容层的轨迹输入转换为规划结果和触发时间轴。
/// </summary>
public sealed class ControllerClientTrajectoryOrchestrator
{
private readonly ICspPlanner _icspPlanner = new();
private readonly SelfAdaptIcspPlanner _selfAdaptIcspPlanner = new();
private readonly ShotTimelineBuilder _shotTimelineBuilder = new(new WaypointTimestampResolver());
/// <summary>
/// 对普通轨迹执行 ICSP 规划。
/// </summary>
/// <param name="robot">当前机器人配置。</param>
/// <param name="waypoints">普通轨迹关节路点。</param>
/// <returns>包含规划轨迹、空触发时间轴和执行结果的结果包。</returns>
public PlannedExecutionBundle PlanOrdinaryTrajectory(
RobotProfile robot,
IReadOnlyList<IReadOnlyList<double>> waypoints)
{
ArgumentNullException.ThrowIfNull(robot);
ArgumentNullException.ThrowIfNull(waypoints);
var program = CreateProgram(
name: "ordinary-trajectory",
waypoints: waypoints,
shotFlags: Enumerable.Repeat(false, waypoints.Count),
offsetValues: Enumerable.Repeat(0, waypoints.Count),
addressGroups: Enumerable.Range(0, waypoints.Count).Select(static _ => Array.Empty<int>()));
var request = new TrajectoryRequest(
robot: robot,
program: program,
method: PlanningMethod.Icsp);
var plannedTrajectory = _icspPlanner.Plan(request);
var shotTimeline = new ShotTimeline(Array.Empty<ShotEvent>(), Array.Empty<TrajectoryDoEvent>());
var result = CreateResult(plannedTrajectory, shotTimeline);
return new PlannedExecutionBundle(plannedTrajectory, shotTimeline, result);
}
/// <summary>
/// 对已经上传的飞拍轨迹执行自适应 ICSP 规划并生成触发时间轴。
/// </summary>
/// <param name="robot">当前机器人配置。</param>
/// <param name="uploaded">兼容层保存的上传轨迹。</param>
/// <returns>包含规划轨迹、触发时间轴和执行结果的结果包。</returns>
public PlannedExecutionBundle PlanUploadedFlyshot(RobotProfile robot, ControllerClientCompatUploadedTrajectory uploaded)
{
ArgumentNullException.ThrowIfNull(robot);
ArgumentNullException.ThrowIfNull(uploaded);
var program = CreateProgram(
name: uploaded.Name,
waypoints: uploaded.Waypoints,
shotFlags: uploaded.ShotFlags,
offsetValues: uploaded.OffsetValues,
addressGroups: uploaded.AddressGroups);
var request = new TrajectoryRequest(
robot: robot,
program: program,
method: PlanningMethod.SelfAdaptIcsp);
var plannedTrajectory = _selfAdaptIcspPlanner.Plan(request);
var shotTimeline = _shotTimelineBuilder.Build(
plannedTrajectory,
holdCycles: 0,
samplePeriod: robot.ServoPeriod);
var result = CreateResult(plannedTrajectory, shotTimeline);
return new PlannedExecutionBundle(plannedTrajectory, shotTimeline, result);
}
/// <summary>
/// 把兼容层输入数组转换成领域层 FlyshotProgram。
/// </summary>
/// <param name="name">轨迹名称。</param>
/// <param name="waypoints">关节路点。</param>
/// <param name="shotFlags">拍照标志。</param>
/// <param name="offsetValues">偏移周期。</param>
/// <param name="addressGroups">IO 地址组。</param>
/// <returns>领域层飞拍程序。</returns>
private static FlyshotProgram CreateProgram(
string name,
IEnumerable<IReadOnlyList<double>> waypoints,
IEnumerable<bool> shotFlags,
IEnumerable<int> offsetValues,
IEnumerable<IReadOnlyList<int>> addressGroups)
{
return new FlyshotProgram(
name: name,
waypoints: waypoints.Select(static waypoint => new JointWaypoint(waypoint)).ToArray(),
shotFlags: shotFlags.ToArray(),
offsetValues: offsetValues.ToArray(),
addressGroups: addressGroups.Select(static group => new IoAddressGroup(group)).ToArray());
}
/// <summary>
/// 从规划轨迹和触发时间轴构造运行时可消费的稳定结果对象。
/// </summary>
/// <param name="plannedTrajectory">规划后的轨迹。</param>
/// <param name="shotTimeline">触发时间轴。</param>
/// <returns>运行时执行结果描述。</returns>
private static TrajectoryResult CreateResult(PlannedTrajectory plannedTrajectory, ShotTimeline shotTimeline)
{
return new TrajectoryResult(
programName: plannedTrajectory.OriginalProgram.Name,
method: plannedTrajectory.Method,
isValid: true,
duration: TimeSpan.FromSeconds(plannedTrajectory.WaypointTimes[^1]),
shotEvents: shotTimeline.ShotEvents,
triggerTimeline: shotTimeline.TriggerTimeline,
artifacts: Array.Empty<TrajectoryArtifact>(),
failureReason: null,
usedCache: false,
originalWaypointCount: plannedTrajectory.OriginalWaypointCount,
plannedWaypointCount: plannedTrajectory.PlannedWaypointCount);
}
}