using System.Text.Json; using Flyshot.ControllerClientCompat; using Microsoft.AspNetCore.Mvc; namespace Flyshot.Server.Host.Controllers; /// /// 提供对 `flyshot-uaes-interface` 既有 FastAPI HTTP 路由层的一比一 MVC 兼容控制器。 /// [ApiController] [Tags("ControllerClient 兼容")] public sealed class LegacyHttpApiController : ControllerBase { private readonly IControllerClientCompatService _compatService; /// /// 初始化旧 HTTP 兼容控制器。 /// /// ControllerClient 兼容服务。 public LegacyHttpApiController(IControllerClientCompatService compatService) { _compatService = compatService ?? throw new ArgumentNullException(nameof(compatService)); } /// /// 兼容旧根路径探活接口。 /// /// 旧 HTTP 服务约定的 Hello World 响应。 [HttpGet("/")] public IActionResult Root() { return Ok(new { message = "Hello World" }); } /// /// 兼容旧 `/connect_server/` 路由;在 replacement 宿主中仅记录调用方期望连接的地址。 /// /// 旧客户端传入的服务端 IP。 /// 旧客户端传入的服务端端口。 /// 与旧 FastAPI 层一致的状态响应。 [HttpPost("/connect_server/")] public IActionResult ConnectServer([FromQuery] string server_ip, [FromQuery] int port) { try { _compatService.ConnectServer(server_ip, port); return Ok(new { status = "connected" }); } catch { return LegacyBadRequest("Connect Server failed"); } } /// /// 兼容旧 `GetServerVersion` 版本查询语义。 /// /// 服务端版本号。 [HttpGet("/get_server_version/")] public IActionResult GetServerVersion() { return Ok(new { server_version = _compatService.GetServerVersion() }); } /// /// 兼容旧 `GetClientVersion` 版本查询语义。 /// /// 客户端版本号。 [HttpGet("/get_client_version/")] public IActionResult GetClientVersion() { return Ok(new { client_version = _compatService.GetClientVersion() }); } /// /// 兼容旧 `/setup_robot/` 路由。 /// /// 旧 HTTP 层使用的机器人名称。 /// 旧 FastAPI 层风格的状态响应。 [HttpPost("/setup_robot/")] public IActionResult SetupRobot([FromQuery] string robot_name) { try { _compatService.SetUpRobot(robot_name); return Ok(new { status = "robot setup" }); } catch { return LegacyBadRequest("SetUpRobot failed"); } } /// /// 兼容旧 `SetUpRobotFromEnv(env_file)` 参数形状。 /// /// 环境文件路径。 /// 旧 FastAPI 层风格的状态响应。 [HttpPost("/setup_robot_from_env/")] public IActionResult SetupRobotFromEnv([FromQuery] string env_file) { try { _compatService.SetUpRobotFromEnv(env_file); return Ok(new { status = "robot setup" }); } catch { return LegacyBadRequest("SetUpRobotFromEnv failed"); } } /// /// 兼容旧 `SetShowTCP(is_show, axis_length, axis_size)` 参数形状。 /// /// 是否显示 TCP。 /// 坐标轴长度。 /// 坐标轴线宽。 /// 旧 FastAPI 层风格的状态响应。 [HttpPost("/set_show_tcp/")] public IActionResult SetShowTcp( [FromQuery] bool is_show = true, [FromQuery] double axis_length = 0.1, [FromQuery] int axis_size = 2) { try { _compatService.SetShowTcp(is_show, axis_length, axis_size); return Ok(new { status = "show TCP set" }); } catch { return LegacyBadRequest("SetShowTCP failed"); } } /// /// 兼容旧 `/is_setup/` 路由。 /// /// 当前机器人是否完成初始化。 [HttpGet("/is_setup/")] public IActionResult IsSetup() { return Ok(new { is_setup = _compatService.IsSetUp }); } /// /// 兼容旧 `EnableRobot(buffer_size=2)` 参数形状。 /// /// 控制器执行缓冲区大小。 /// 旧 FastAPI 层风格的布尔状态响应。 [HttpGet("/enable_robot/")] public IActionResult EnableRobot([FromQuery] int buffer_size = 2) { try { _compatService.EnableRobot(buffer_size); return Ok(new { enable_robot = true }); } catch { return LegacyBadRequest("EnableRobot failed"); } } /// /// 兼容旧 `/disable_robot/` 路由。 /// /// 旧 FastAPI 层风格的布尔状态响应。 [HttpGet("/disable_robot/")] public IActionResult DisableRobot() { try { _compatService.DisableRobot(); return Ok(new { disable_robot = true }); } catch { return LegacyBadRequest("DisableRobot failed"); } } /// /// 提供与旧客户端 StopMove 语义对应的 HTTP 端点。 /// /// 旧 FastAPI 层风格的状态响应。 [HttpGet("/stop_move/")] public IActionResult StopMove() { try { _compatService.StopMove(); return Ok(new { status = "move stopped" }); } catch { return LegacyBadRequest("StopMove failed"); } } /// /// 兼容旧 `/set_active_controller/` 路由。 /// /// 是否切到仿真控制器。 /// 旧 FastAPI 层风格的状态响应。 [HttpPost("/set_active_controller/")] public IActionResult SetActiveController([FromQuery] bool sim) { try { _compatService.SetActiveController(sim); return Ok(new { status = "active controller set" }); } catch { return LegacyBadRequest("SetActiveController failed"); } } /// /// 兼容旧 `/connect_robot/` 路由。 /// /// 控制器 IP。 /// 旧 FastAPI 层风格的状态响应。 [HttpPost("/connect_robot/")] public IActionResult ConnectRobot([FromQuery] string ip) { try { _compatService.Connect(ip); return Ok(new { status = "robot connected" }); } catch { return LegacyBadRequest("Connect failed"); } } /// /// 提供与旧客户端 Disconnect 语义对应的 HTTP 端点。 /// /// 旧 FastAPI 层风格的状态响应。 [HttpPost("/disconnect_robot/")] public IActionResult DisconnectRobot() { try { _compatService.Disconnect(); return Ok(new { status = "robot disconnected" }); } catch { return LegacyBadRequest("Disconnect failed"); } } /// /// 兼容旧 `/robot_info/` 路由。 /// /// 旧 HTTP 层聚合的机器人元信息。 [HttpGet("/robot_info/")] public IActionResult GetRobotInfo() { try { return Ok(new { name = _compatService.GetRobotName(), server_version = _compatService.ServerVersion, dof = _compatService.GetDegreesOfFreedom(), speed_ratio = _compatService.GetSpeedRatio() }); } catch { return LegacyBadRequest("GetRobotInfo failed"); } } /// /// 兼容旧 `/set_tcp/` 路由。 /// /// 三维 TCP 请求体。 /// 旧 FastAPI 层风格的状态响应。 [HttpPost("/set_tcp/")] public IActionResult SetTcp([FromBody] LegacyTcpRequest tcp_data) { try { _compatService.SetTcp(tcp_data.x, tcp_data.y, tcp_data.z); return Ok(new { status = "TCP set" }); } catch { return LegacyBadRequest("SetTCP failed"); } } /// /// 兼容旧 `/get_tcp/` 路由。 /// /// 当前 TCP 三维坐标。 [HttpGet("/get_tcp/")] public IActionResult GetTcp() { try { return Ok(new { tcp = _compatService.GetTcp() }); } catch { return LegacyBadRequest("GetTCP failed"); } } /// /// 兼容旧 `/set_io/` 路由。 /// /// IO 端口号。 /// IO 值。 /// IO 类型字符串。 /// 旧 FastAPI 层风格的状态响应。 [HttpPost("/set_io/")] public IActionResult SetIo([FromQuery] int port, [FromQuery] bool value, [FromQuery] string io_type) { try { _compatService.SetIo(port, value, io_type); return Ok(new { status = "IO set" }); } catch { return LegacyBadRequest("SetDigitalOutput failed"); } } /// /// 兼容旧 `/get_io/` 路由。 /// /// IO 端口号。 /// IO 类型字符串。 /// 当前 IO 值。 [HttpGet("/get_io/")] public IActionResult GetIo([FromQuery] int port, [FromQuery] string io_type) { try { return Ok(new { value = _compatService.GetIo(port, io_type) }); } catch { return LegacyBadRequest("GetDigitalOutput failed"); } } /// /// 兼容旧 `/get_joint_position/` 路由。 /// /// 旧 HTTP 层定义的关节位置 JSON 外形。 [HttpGet("/get_joint_position/")] public IActionResult GetJointPosition() { try { return Ok(new { success = true, points = _compatService.GetJointPositions() }); } catch { return LegacyBadRequest("GetJointPosition failed"); } } /// /// 兼容旧 `/move_joint/` 路由。 /// /// 关节位置请求体。 /// 旧 FastAPI 层风格的状态响应。 [HttpPost("/move_joint/")] public IActionResult MoveJoint([FromBody] LegacyJointPositionRequest joint_data) { try { _compatService.MoveJoint(joint_data.joints); return Ok(new { status = "robot moved" }); } catch { return LegacyBadRequest("MoveJoint failed"); } } /// /// 兼容旧 `GetNearestIK(pose, seed, ik)` 参数形状。 /// /// IK 请求体。 /// IK 结果。 [HttpPost("/get_nearest_ik/")] public IActionResult GetNearestIk([FromBody] LegacyNearestIkRequest request) { try { return Ok(new { success = true, ik = _compatService.GetNearestIk(request.pose, request.seed) }); } catch (NotSupportedException exception) { return StatusCode(StatusCodes.Status501NotImplemented, new { detail = exception.Message }); } catch { return LegacyBadRequest("GetNearestIK failed"); } } /// /// 兼容旧 `/list_flyShotTraj/` 路由。 /// /// 已上传飞拍轨迹名称列表。 [HttpGet("/list_flyShotTraj/")] public IActionResult ListFlyshotTrajectories() { var names = _compatService.ListTrajectoryNames(); if (names.Count == 0) { return LegacyBadRequest("ListFlyShotTraj failed"); } return Ok(new { flyshot_trajs = names }); } /// /// 兼容旧 `/execute_trajectory/` 路由,并接受两种历史请求体形状。 /// /// 轨迹请求体。 /// 查询字符串中的 method 覆盖值(兼容历史调用方式)。 /// 查询字符串中的 save_traj 覆盖值(兼容历史调用方式)。 /// 旧 FastAPI 层风格的状态响应。 [HttpPost("/execute_trajectory/")] public IActionResult ExecuteTrajectory( [FromBody] JsonElement waypoints, [FromQuery] string? method = null, [FromQuery] bool? save_traj = null) { try { var request = ParseExecuteTrajectoryRequest(waypoints, method, save_traj); _compatService.ExecuteTrajectory( request.Waypoints, new TrajectoryExecutionOptions(request.Method, request.SaveTrajectory)); return Ok(new { status = "trajectory executed" }); } catch { return LegacyBadRequest("ExecuteTrajectory failed"); } } /// /// 兼容旧 `/upload_flyshot/` 路由。 /// /// 飞拍上传请求体。 /// 旧 FastAPI 层风格的状态响应。 [HttpPost("/upload_flyshot/")] public IActionResult UploadFlyshot([FromBody] LegacyFlightTrajectoryRequest trajectory_data) { if (trajectory_data.shot_flags.Count != trajectory_data.waypoints.Count) { return LegacyValidationError("shot_flags长度必须与路点数量相同"); } if (trajectory_data.offset_values.Count != trajectory_data.waypoints.Count) { return LegacyValidationError("offset_values长度必须与路点数量相同"); } if (trajectory_data.addrs.Count != trajectory_data.waypoints.Count) { return LegacyValidationError("addrs长度必须与路点数量相同"); } try { var trajectory = new ControllerClientCompatUploadedTrajectory( name: trajectory_data.name, waypoints: trajectory_data.waypoints, shotFlags: trajectory_data.shot_flags, offsetValues: trajectory_data.offset_values.Select(static value => (int)value), addressGroups: trajectory_data.addrs); _compatService.UploadTrajectory(trajectory); return Ok(new { status = "FlyShot uploaded" }); } catch { return LegacyBadRequest("UploadFlyShotTraj failed"); } } /// /// 兼容旧 `/execute_flyshot/` 路由。 /// /// 包含轨迹名称和执行参数的请求体。 /// 旧 FastAPI 层风格的状态响应。 [HttpPost("/execute_flyshot/")] public IActionResult ExecuteFlyshot([FromBody] LegacyExecuteFlyshotRequest data) { try { _compatService.ExecuteTrajectoryByName( data.name, new FlyshotExecutionOptions( moveToStart: data.move_to_start, method: data.method, saveTrajectory: data.save_traj, useCache: data.use_cache)); return Ok(new { status = "FlyShot executed", success = true }); } catch (Exception exception) { return StatusCode(StatusCodes.Status500InternalServerError, new { detail = exception.Message }); } } /// /// 兼容旧 `SaveTrajInfo(name, method)` 参数形状。 /// /// 轨迹保存请求体。 /// 旧 FastAPI 层风格的状态响应。 [HttpPost("/save_traj_info/")] public IActionResult SaveTrajectoryInfo([FromBody] LegacyTrajectoryInfoRequest request) { try { _compatService.SaveTrajectoryInfo(request.name, request.method); return Ok(new { status = "trajectory info saved", success = true }); } catch (NotSupportedException exception) { return StatusCode(StatusCodes.Status501NotImplemented, new { detail = exception.Message }); } catch { return LegacyBadRequest("SaveTrajInfo failed"); } } /// /// 兼容旧 `IsFlyShotTrajValid(time, name, method, save_traj)` 参数形状。 /// /// 轨迹有效性检查请求体。 /// 有效性和轨迹时长。 [HttpPost("/is_flyShotTrajValid/")] public IActionResult IsFlyshotTrajectoryValid([FromBody] LegacyFlyshotValidationRequest request) { try { var isValid = _compatService.IsFlyshotTrajectoryValid( out var duration, request.name, request.method, request.save_traj); return Ok(new { success = isValid, valid = isValid, time = duration.TotalSeconds }); } catch (NotSupportedException exception) { return StatusCode(StatusCodes.Status501NotImplemented, new { detail = exception.Message }); } catch { return LegacyBadRequest("IsFlyShotTrajValid failed"); } } /// /// 兼容旧 `/set_speedRatio/` 路由。 /// /// 速度倍率请求体。 /// 旧 FastAPI 层风格的状态响应。 [HttpPost("/set_speedRatio/")] public IActionResult SetSpeedRatio([FromBody] LegacySpeedRatioRequest data) { try { _compatService.SetSpeedRatio(data.speed); return Ok(new { message = "set_speedRatio executed", returnCode = 0 }); } catch { return LegacyBadRequest("set_speedRatio failed"); } } /// /// 兼容旧 `/delete_flyshot/` 路由。 /// /// 包含轨迹名称的请求体。 /// 旧 FastAPI 层风格的状态响应。 [HttpPost("/delete_flyshot/")] public IActionResult DeleteFlyshot([FromBody] LegacyNameRequest request) { try { _compatService.DeleteTrajectory(request.name); return Ok(new { status = "FlyShot deleted" }); } catch { return LegacyBadRequest("DeleteFlyShotTraj failed"); } } /// /// 兼容旧 `/init_mpc_robt` 路由,保留历史拼写。 /// /// 初始化请求体。 /// 旧 FastAPI 层风格的初始化结果。 [HttpPost("/init_mpc_robt")] public IActionResult InitMpcRobot([FromBody] LegacyInitMpcRobotRequest data) { try { _compatService.ConnectServer(data.server_ip, data.port); _compatService.SetUpRobot(data.robot_name); if (!_compatService.IsSetUp) { return LegacyBadRequest("Robot not setup"); } _compatService.SetActiveController(data.sim); _compatService.Connect(data.robot_ip); _compatService.EnableRobot(2); return Ok(new { message = "init_Success", returnCode = 0 }); } catch { return LegacyBadRequest("Connect Server failed"); } } /// /// 兼容旧 `/get_pose` 路由。 /// /// 当前末端位姿数组。 [HttpGet("/get_pose")] public IActionResult GetPose() { try { return Ok(new { pose = _compatService.GetPose() }); } catch { return LegacyBadRequest("GetPose failed"); } } /// /// 解析旧 `/execute_trajectory/` 的完整参数形状。 /// /// 原始 JSON 请求体。 /// 查询字符串中的 method 覆盖值。 /// 查询字符串中的 save_traj 覆盖值。 /// 统一后的路点和执行参数。 private static ( IReadOnlyList> Waypoints, string Method, bool SaveTrajectory) ParseExecuteTrajectoryRequest( JsonElement payload, string? queryMethod, bool? querySaveTrajectory) { string method = queryMethod ?? "icsp"; bool saveTrajectory = querySaveTrajectory ?? false; if (payload.ValueKind == JsonValueKind.Object) { if (payload.TryGetProperty("method", out var methodElement) && methodElement.ValueKind == JsonValueKind.String) { method = methodElement.GetString() ?? method; } if (payload.TryGetProperty("save_traj", out var saveTrajectoryElement)) { saveTrajectory = saveTrajectoryElement.GetBoolean(); } if (!payload.TryGetProperty("waypoints", out var waypointElement)) { throw new InvalidOperationException("ExecuteTrajectory request body must include waypoints."); } return (ParseLegacyTrajectoryWaypoints(waypointElement), method, saveTrajectory); } return (ParseLegacyTrajectoryWaypoints(payload), method, saveTrajectory); } /// /// 解析旧 `/execute_trajectory/` 可能出现的两种历史请求体形状。 /// /// 原始 JSON 请求体。 /// 统一后的关节路点集合。 private static IReadOnlyList> ParseLegacyTrajectoryWaypoints(JsonElement waypoints) { if (waypoints.ValueKind != JsonValueKind.Array) { throw new InvalidOperationException("ExecuteTrajectory request body must be an array."); } var parsedWaypoints = new List>(); foreach (var waypointElement in waypoints.EnumerateArray()) { if (waypointElement.ValueKind == JsonValueKind.Array) { parsedWaypoints.Add(waypointElement.EnumerateArray().Select(static value => value.GetDouble()).ToArray()); continue; } if (waypointElement.ValueKind == JsonValueKind.Object && waypointElement.TryGetProperty("joints", out var jointElement)) { parsedWaypoints.Add(jointElement.EnumerateArray().Select(static value => value.GetDouble()).ToArray()); continue; } throw new InvalidOperationException("Unsupported waypoint payload shape."); } return parsedWaypoints; } /// /// 构造与旧 FastAPI `HTTPException(status_code=400, detail=...)` 等价的响应。 /// /// 错误详情文本。 /// 400 JSON 响应。 private BadRequestObjectResult LegacyBadRequest(string detail) { return BadRequest(new { detail }); } /// /// 构造与旧 FastAPI `422` 输入校验失败等价的响应。 /// /// 错误详情文本。 /// 422 JSON 响应。 private ObjectResult LegacyValidationError(string detail) { return StatusCode(StatusCodes.Status422UnprocessableEntity, new { detail }); } } /// /// 表示旧 `/set_tcp/` 路由使用的三维 TCP 请求体。 /// public sealed class LegacyTcpRequest { /// /// 获取或设置 TCP X。 /// public double x { get; init; } /// /// 获取或设置 TCP Y。 /// public double y { get; init; } /// /// 获取或设置 TCP Z。 /// public double z { get; init; } } /// /// 表示旧 `/move_joint/` 路由使用的关节请求体。 /// public sealed class LegacyJointPositionRequest { /// /// 获取或设置目标关节数组。 /// public List joints { get; init; } = []; } /// /// 表示旧 `/upload_flyshot/` 路由使用的飞拍上传请求体。 /// public sealed class LegacyFlightTrajectoryRequest { /// /// 获取或设置地址组集合。 /// public List> addrs { get; init; } = []; /// /// 获取或设置飞拍轨迹名称。 /// public string name { get; init; } = string.Empty; /// /// 获取或设置偏移周期集合。 /// public List offset_values { get; init; } = []; /// /// 获取或设置拍照标志集合。 /// public List shot_flags { get; init; } = []; /// /// 获取或设置关节路点集合。 /// public List> waypoints { get; init; } = []; } /// /// 表示旧 `/execute_flyshot/` 与 `/delete_flyshot/` 路由使用的名称请求体。 /// public sealed class LegacyNameRequest { /// /// 获取或设置轨迹名称。 /// public string name { get; init; } = string.Empty; } /// /// 表示旧 `/execute_flyshot/` 路由使用的完整执行请求体。 /// public sealed class LegacyExecuteFlyshotRequest { /// /// 获取或设置轨迹名称。 /// public string name { get; init; } = string.Empty; /// /// 获取或设置是否先移动到轨迹起点。 /// public bool move_to_start { get; init; } = true; /// /// 获取或设置轨迹生成方法。 /// public string method { get; init; } = "icsp"; /// /// 获取或设置是否保存轨迹信息。 /// public bool save_traj { get; init; } = true; /// /// 获取或设置是否复用轨迹缓存。 /// public bool use_cache { get; init; } = true; } /// /// 表示旧 `SaveTrajInfo` 参数形状。 /// public sealed class LegacyTrajectoryInfoRequest { /// /// 获取或设置轨迹名称。 /// public string name { get; init; } = string.Empty; /// /// 获取或设置轨迹生成方法。 /// public string method { get; init; } = "icsp"; } /// /// 表示旧 `IsFlyShotTrajValid` 参数形状。 /// public sealed class LegacyFlyshotValidationRequest { /// /// 获取或设置轨迹名称。 /// public string name { get; init; } = string.Empty; /// /// 获取或设置轨迹生成方法。 /// public string method { get; init; } = "icsp"; /// /// 获取或设置是否保存轨迹信息。 /// public bool save_traj { get; init; } = true; } /// /// 表示旧 `GetNearestIK` 参数形状。 /// public sealed class LegacyNearestIkRequest { /// /// 获取或设置目标位姿 `[x,y,z,qx,qy,qz,qw]`。 /// public List pose { get; init; } = []; /// /// 获取或设置 IK seed 关节数组。 /// public List seed { get; init; } = []; } /// /// 表示旧 `/set_speedRatio/` 路由使用的速度倍率请求体。 /// public sealed class LegacySpeedRatioRequest { /// /// 获取或设置目标速度倍率。 /// public double speed { get; init; } } /// /// 表示旧 `/init_mpc_robt` 路由使用的初始化请求体。 /// public sealed class LegacyInitMpcRobotRequest { /// /// 获取或设置目标服务端 IP。 /// public string server_ip { get; init; } = string.Empty; /// /// 获取或设置目标服务端端口。 /// public int port { get; init; } /// /// 获取或设置机器人名称。 /// public string robot_name { get; init; } = string.Empty; /// /// 获取或设置机器人控制器 IP。 /// public string robot_ip { get; init; } = string.Empty; /// /// 获取或设置是否使用仿真控制器;默认 false 连接真机。 /// public bool sim { get; init; } }