Files
FlyShotHost/src/Flyshot.Server.Host/Controllers/LegacyHttpApiController.cs
yunxiao.zhu 2cd42f04e5 feat(fanuc): 添加直角坐标点动功能与相关接口
* 新增 `MovePose` 方法,支持以直角坐标执行点到点移动。
* 引入 `LegacyCartesianPoseRequest` 类,处理直角位姿请求体的解析与验证。
* 更新 `LegacyHttpApiController`,实现 `/move_pose/` 路由以支持新功能。
* 增强状态快照元数据,提供机器人初始化状态与已上传轨迹信息。
* 更新前端状态页面,增加直角坐标点动控制面板与步长设置选项。
* 相关文档与测试用例同步更新,确保新功能的完整性与稳定性。
2026-05-14 17:46:42 +08:00

1205 lines
43 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System.Text.Json;
using Flyshot.ControllerClientCompat;
using Microsoft.AspNetCore.Mvc;
namespace Flyshot.Server.Host.Controllers;
/// <summary>
/// 提供对 `flyshot-uaes-interface` 既有 FastAPI HTTP 路由层的一比一 MVC 兼容控制器。
/// </summary>
[ApiController]
[Tags("ControllerClient 兼容")]
public sealed class LegacyHttpApiController : ControllerBase
{
private readonly IControllerClientCompatService _compatService;
private readonly ILogger<LegacyHttpApiController> _logger;
/// <summary>
/// 初始化旧 HTTP 兼容控制器。
/// </summary>
/// <param name="compatService">ControllerClient 兼容服务。</param>
/// <param name="logger">日志记录器。</param>
public LegacyHttpApiController(IControllerClientCompatService compatService, ILogger<LegacyHttpApiController> logger)
{
_compatService = compatService ?? throw new ArgumentNullException(nameof(compatService));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
/// <summary>
/// 兼容旧根路径探活接口。
/// </summary>
/// <returns>旧 HTTP 服务约定的 Hello World 响应。</returns>
[HttpGet("/")]
public IActionResult Root()
{
return Ok(new { message = "Hello World" });
}
/// <summary>
/// 兼容旧 `/connect_server/` 路由;在 replacement 宿主中仅记录调用方期望连接的地址。
/// </summary>
/// <param name="server_ip">旧客户端传入的服务端 IP。</param>
/// <param name="port">旧客户端传入的服务端端口。</param>
/// <returns>与旧 FastAPI 层一致的状态响应。</returns>
[HttpPost("/connect_server/")]
public IActionResult ConnectServer([FromQuery] string server_ip, [FromQuery] int port)
{
_logger.LogInformation("ConnectServer 调用: server_ip={ServerIp}, port={Port}", server_ip, port);
try
{
_compatService.ConnectServer(server_ip, port);
_logger.LogInformation("ConnectServer 成功: server_ip={ServerIp}, port={Port}", server_ip, port);
return Ok(new { status = "connected" });
}
catch (Exception exception)
{
_logger.LogError(exception, "ConnectServer 失败: server_ip={ServerIp}, port={Port}", server_ip, port);
return LegacyBadRequest("Connect Server failed");
}
}
/// <summary>
/// 兼容旧 `GetServerVersion` 版本查询语义。
/// </summary>
/// <returns>服务端版本号。</returns>
[HttpGet("/get_server_version/")]
public IActionResult GetServerVersion()
{
return Ok(new { server_version = _compatService.GetServerVersion() });
}
/// <summary>
/// 兼容旧 `GetClientVersion` 版本查询语义。
/// </summary>
/// <returns>客户端版本号。</returns>
[HttpGet("/get_client_version/")]
public IActionResult GetClientVersion()
{
return Ok(new { client_version = _compatService.GetClientVersion() });
}
/// <summary>
/// 兼容旧 `/setup_robot/` 路由。
/// </summary>
/// <param name="robot_name">旧 HTTP 层使用的机器人名称。</param>
/// <returns>旧 FastAPI 层风格的状态响应。</returns>
[HttpPost("/setup_robot/")]
public IActionResult SetupRobot([FromQuery] string robot_name)
{
_logger.LogInformation("SetupRobot 调用: robot_name={RobotName}", robot_name);
try
{
_compatService.SetUpRobot(robot_name);
_logger.LogInformation("SetupRobot 成功: robot_name={RobotName}", robot_name);
return Ok(new { status = "robot setup" });
}
catch (Exception exception)
{
_logger.LogError(exception, "SetupRobot 失败: robot_name={RobotName}", robot_name);
return LegacyBadRequest("SetUpRobot failed");
}
}
/// <summary>
/// 兼容旧 `SetUpRobotFromEnv(env_file)` 参数形状。
/// </summary>
/// <param name="env_file">环境文件路径。</param>
/// <returns>旧 FastAPI 层风格的状态响应。</returns>
[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");
}
}
/// <summary>
/// 兼容旧 `SetShowTCP(is_show, axis_length, axis_size)` 参数形状。
/// </summary>
/// <param name="is_show">是否显示 TCP。</param>
/// <param name="axis_length">坐标轴长度。</param>
/// <param name="axis_size">坐标轴线宽。</param>
/// <returns>旧 FastAPI 层风格的状态响应。</returns>
[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");
}
}
/// <summary>
/// 兼容旧 `/is_setup/` 路由。
/// </summary>
/// <returns>当前机器人是否完成初始化。</returns>
[HttpGet("/is_setup/")]
public IActionResult IsSetup()
{
return Ok(new { is_setup = _compatService.IsSetUp });
}
/// <summary>
/// 兼容旧 `EnableRobot(buffer_size=4)` 参数形状。
/// </summary>
/// <param name="buffer_size">控制器执行缓冲区大小。</param>
/// <returns>旧 FastAPI 层风格的布尔状态响应。</returns>
[HttpGet("/enable_robot/")]
public IActionResult EnableRobot([FromQuery] int buffer_size = 4)
{
_logger.LogInformation("EnableRobot 调用: buffer_size={BufferSize}", buffer_size);
try
{
_compatService.EnableRobot(buffer_size);
_logger.LogInformation("EnableRobot 成功");
return Ok(new { enable_robot = true });
}
catch (Exception exception)
{
_logger.LogError(exception, "EnableRobot 失败");
return LegacyBadRequest("EnableRobot failed");
}
}
/// <summary>
/// 兼容旧 `/disable_robot/` 路由。
/// </summary>
/// <returns>旧 FastAPI 层风格的布尔状态响应。</returns>
[HttpGet("/disable_robot/")]
public IActionResult DisableRobot()
{
_logger.LogInformation("DisableRobot 调用");
try
{
_compatService.DisableRobot();
_logger.LogInformation("DisableRobot 成功");
return Ok(new { disable_robot = true });
}
catch (Exception exception)
{
_logger.LogError(exception, "DisableRobot 失败");
return LegacyBadRequest("DisableRobot failed");
}
}
/// <summary>
/// 提供与旧客户端 <c>StopMove</c> 语义对应的 HTTP 端点。
/// </summary>
/// <returns>旧 FastAPI 层风格的状态响应。</returns>
[HttpGet("/stop_move/")]
public IActionResult StopMove()
{
_logger.LogInformation("StopMove 调用");
try
{
_compatService.StopMove();
_logger.LogInformation("StopMove 成功");
return Ok(new { status = "move stopped" });
}
catch (Exception exception)
{
_logger.LogError(exception, "StopMove 失败");
return LegacyBadRequest("StopMove failed");
}
}
/// <summary>
/// 兼容旧 `/set_active_controller/` 路由。
/// </summary>
/// <param name="sim">是否切到仿真控制器。</param>
/// <returns>旧 FastAPI 层风格的状态响应。</returns>
[HttpPost("/set_active_controller/")]
public IActionResult SetActiveController([FromQuery] bool sim)
{
_logger.LogInformation("SetActiveController 调用: sim={Sim}", sim);
try
{
_compatService.SetActiveController(sim);
_logger.LogInformation("SetActiveController 成功: sim={Sim}", sim);
return Ok(new { status = "active controller set" });
}
catch (Exception exception)
{
_logger.LogError(exception, "SetActiveController 失败: sim={Sim}", sim);
return LegacyBadRequest("SetActiveController failed");
}
}
/// <summary>
/// 兼容旧 `/connect_robot/` 路由。
/// </summary>
/// <param name="ip">控制器 IP。</param>
/// <returns>旧 FastAPI 层风格的状态响应。</returns>
[HttpPost("/connect_robot/")]
public IActionResult ConnectRobot([FromQuery] string ip)
{
_logger.LogInformation("ConnectRobot 调用: ip={Ip}", ip);
try
{
_compatService.Connect(ip);
_logger.LogInformation("ConnectRobot 成功: ip={Ip}", ip);
return Ok(new { status = "robot connected" });
}
catch (Exception exception)
{
_logger.LogError(exception, "ConnectRobot 失败: ip={Ip}", ip);
return LegacyBadRequest("Connect failed");
}
}
/// <summary>
/// 提供与旧客户端 <c>Disconnect</c> 语义对应的 HTTP 端点。
/// </summary>
/// <returns>旧 FastAPI 层风格的状态响应。</returns>
[HttpPost("/disconnect_robot/")]
public IActionResult DisconnectRobot()
{
_logger.LogInformation("DisconnectRobot 调用");
try
{
_compatService.Disconnect();
_logger.LogInformation("DisconnectRobot 成功");
return Ok(new { status = "robot disconnected" });
}
catch (Exception exception)
{
_logger.LogError(exception, "DisconnectRobot 失败");
return LegacyBadRequest("Disconnect failed");
}
}
/// <summary>
/// 兼容旧 `/robot_info/` 路由。
/// </summary>
/// <returns>旧 HTTP 层聚合的机器人元信息。</returns>
[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");
}
}
/// <summary>
/// 兼容旧 `/set_tcp/` 路由。
/// </summary>
/// <param name="tcp_data">三维 TCP 请求体。</param>
/// <returns>旧 FastAPI 层风格的状态响应。</returns>
[HttpPost("/set_tcp/")]
public IActionResult SetTcp([FromBody] LegacyTcpRequest tcp_data)
{
_logger.LogInformation("SetTcp 调用: x={X}, y={Y}, z={Z}", tcp_data.x, tcp_data.y, tcp_data.z);
try
{
_compatService.SetTcp(tcp_data.x, tcp_data.y, tcp_data.z);
_logger.LogInformation("SetTcp 成功");
return Ok(new { status = "TCP set" });
}
catch (Exception exception)
{
_logger.LogError(exception, "SetTcp 失败");
return LegacyBadRequest("SetTCP failed");
}
}
/// <summary>
/// 兼容旧 `/get_tcp/` 路由。
/// </summary>
/// <returns>当前 TCP 三维坐标。</returns>
[HttpGet("/get_tcp/")]
public IActionResult GetTcp()
{
try
{
return Ok(new { tcp = _compatService.GetTcp() });
}
catch
{
return LegacyBadRequest("GetTCP failed");
}
}
/// <summary>
/// 兼容旧 `/set_io/` 路由。
/// </summary>
/// <param name="port">IO 端口号。</param>
/// <param name="value">IO 值。</param>
/// <param name="io_type">IO 类型字符串。</param>
/// <returns>旧 FastAPI 层风格的状态响应。</returns>
[HttpPost("/set_io/")]
public IActionResult SetIo([FromQuery] int port, [FromQuery] bool value, [FromQuery] string io_type)
{
_logger.LogInformation("SetIo 调用: port={Port}, value={Value}, io_type={IoType}", port, value, io_type);
try
{
_compatService.SetIo(port, value, io_type);
_logger.LogInformation("SetIo 成功: port={Port}, value={Value}", port, value);
return Ok(new { status = "IO set" });
}
catch (Exception exception)
{
_logger.LogError(exception, "SetIo 失败: port={Port}, value={Value}", port, value);
return LegacyBadRequest("SetDigitalOutput failed");
}
}
/// <summary>
/// 兼容旧 `/get_io/` 路由。
/// </summary>
/// <param name="port">IO 端口号。</param>
/// <param name="io_type">IO 类型字符串。</param>
/// <returns>当前 IO 值。</returns>
[HttpGet("/get_io/")]
public IActionResult GetIo([FromQuery] int port, [FromQuery] string io_type)
{
_logger.LogInformation("GetIo 调用: port={Port}, io_type={IoType}", port, io_type);
try
{
var value = _compatService.GetIo(port, io_type);
_logger.LogInformation("GetIo 成功: port={Port}, value={Value}", port, value);
return Ok(new { value });
}
catch (Exception exception)
{
_logger.LogError(exception, "GetIo 失败: port={Port}", port);
return LegacyBadRequest("GetDigitalOutput failed");
}
}
/// <summary>
/// 兼容旧 `/get_joint_position/` 路由。
/// </summary>
/// <returns>旧 HTTP 层定义的关节位置 JSON 外形。</returns>
[HttpGet("/get_joint_position/")]
public IActionResult GetJointPosition()
{
try
{
return Ok(new { success = true, points = _compatService.GetJointPositions() });
}
catch
{
return LegacyBadRequest("GetJointPosition failed");
}
}
/// <summary>
/// 兼容旧 `/move_joint/` 路由。
/// </summary>
/// <param name="joint_data">关节位置请求体。</param>
/// <returns>旧 FastAPI 层风格的状态响应。</returns>
[HttpPost("/move_joint/")]
public IActionResult MoveJoint([FromBody] LegacyJointPositionRequest joint_data)
{
_logger.LogInformation("MoveJoint 调用: 关节数={JointCount}", joint_data.joints.Count);
_logger.LogDebug("MoveJoint 路点: {Joints}", string.Join(", ", joint_data.joints.Select(j => j.ToString("F4"))));
try
{
_compatService.MoveJoint(joint_data.joints);
_logger.LogInformation("MoveJoint 成功");
return Ok(new { status = "robot moved" });
}
catch (Exception exception)
{
_logger.LogError(exception, "MoveJoint 失败");
return LegacyBadRequest("MoveJoint failed");
}
}
/// <summary>
/// 以直角坐标 `[x,y,z,w,p,r]` 执行点到点移动。
/// </summary>
/// <param name="pose_data">直角位姿请求体。</param>
/// <returns>旧 FastAPI 层风格的状态响应。</returns>
[HttpPost("/move_pose/")]
public IActionResult MovePose([FromBody] JsonElement pose_data)
{
try
{
var poseRequest = LegacyCartesianPoseRequest.FromJson(pose_data);
var pose = poseRequest.ToPoseArray();
_logger.LogInformation("MovePose 调用: x={X}, y={Y}, z={Z}, w={W}, p={P}, r={R}",
poseRequest.x, poseRequest.y, poseRequest.z, poseRequest.w, poseRequest.p, poseRequest.r);
_compatService.MovePose(pose);
_logger.LogInformation("MovePose 成功");
return Ok(new { status = "robot moved" });
}
catch (Exception exception)
{
_logger.LogError(exception, "MovePose 失败");
return LegacyBadRequest("MovePose failed");
}
}
/// <summary>
/// 兼容旧 `GetNearestIK(pose, seed, ik)` 参数形状。
/// </summary>
/// <param name="request">IK 请求体。</param>
/// <returns>IK 结果。</returns>
[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");
}
}
/// <summary>
/// 兼容旧 `/list_flyShotTraj/` 路由。
/// </summary>
/// <returns>已上传飞拍轨迹名称列表。</returns>
[HttpGet("/list_flyShotTraj/")]
public IActionResult ListFlyshotTrajectories()
{
var names = _compatService.ListTrajectoryNames();
if (names.Count == 0)
{
return LegacyBadRequest("ListFlyShotTraj failed");
}
return Ok(new { flyshot_trajs = names });
}
/// <summary>
/// 兼容旧 `/execute_trajectory/` 路由,并接受两种历史请求体形状。
/// </summary>
/// <param name="waypoints">轨迹请求体。</param>
/// <param name="method">查询字符串中的 method 覆盖值(兼容历史调用方式)。</param>
/// <param name="save_traj">查询字符串中的 save_traj 覆盖值(兼容历史调用方式)。</param>
/// <returns>旧 FastAPI 层风格的状态响应。</returns>
[HttpPost("/execute_trajectory/")]
public IActionResult ExecuteTrajectory(
[FromBody] JsonElement waypoints,
[FromQuery] string? method = null,
[FromQuery] bool? save_traj = null)
{
_logger.LogInformation("ExecuteTrajectory 调用: method={Method}, save_traj={SaveTraj}", method ?? "icsp", save_traj ?? false);
try
{
var request = ParseExecuteTrajectoryRequest(waypoints, method, save_traj);
_logger.LogDebug("ExecuteTrajectory 路点数={WaypointCount}, method={Method}", request.Waypoints.Count, request.Method);
_compatService.ExecuteTrajectory(
request.Waypoints,
new TrajectoryExecutionOptions(request.Method, request.SaveTrajectory));
_logger.LogInformation("ExecuteTrajectory 成功: method={Method}", request.Method);
return Ok(new { status = "trajectory executed" });
}
catch (Exception exception)
{
_logger.LogError(exception, "ExecuteTrajectory 失败");
return LegacyBadRequest("ExecuteTrajectory failed");
}
}
/// <summary>
/// 兼容旧 `/upload_flyshot/` 路由。
/// </summary>
/// <param name="trajectory_data">飞拍上传请求体。</param>
/// <returns>旧 FastAPI 层风格的状态响应。</returns>
[HttpPost("/upload_flyshot/")]
public IActionResult UploadFlyshot([FromBody] LegacyFlightTrajectoryRequest trajectory_data)
{
_logger.LogInformation(
"UploadFlyshot 调用: name={Name}, waypoints={WaypointCount}, shot_flags={ShotCount}",
trajectory_data.name,
trajectory_data.waypoints.Count,
trajectory_data.shot_flags.Count(static f => f));
if (trajectory_data.shot_flags.Count != trajectory_data.waypoints.Count)
{
_logger.LogWarning("UploadFlyshot 校验失败: shot_flags长度({ShotFlagsCount}) != 路点数({WaypointCount})",
trajectory_data.shot_flags.Count, trajectory_data.waypoints.Count);
return LegacyValidationError("shot_flags长度必须与路点数量相同");
}
if (trajectory_data.offset_values.Count != trajectory_data.waypoints.Count)
{
_logger.LogWarning("UploadFlyshot 校验失败: offset_values长度({OffsetCount}) != 路点数({WaypointCount})",
trajectory_data.offset_values.Count, trajectory_data.waypoints.Count);
return LegacyValidationError("offset_values长度必须与路点数量相同");
}
if (trajectory_data.addrs.Count != trajectory_data.waypoints.Count)
{
_logger.LogWarning("UploadFlyshot 校验失败: addrs长度({AddrCount}) != 路点数({WaypointCount})",
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);
_logger.LogInformation("UploadFlyshot 成功: name={Name}", trajectory_data.name);
return Ok(new { status = "FlyShot uploaded" });
}
catch (Exception exception)
{
_logger.LogError(exception, "UploadFlyshot 失败: name={Name}", trajectory_data.name);
return LegacyBadRequest("UploadFlyShotTraj failed");
}
}
/// <summary>
/// 兼容旧 `/execute_flyshot/` 路由。
/// </summary>
/// <param name="data">包含轨迹名称和执行参数的请求体。</param>
/// <returns>旧 FastAPI 层风格的状态响应。</returns>
[HttpPost("/execute_flyshot/")]
public IActionResult ExecuteFlyshot([FromBody] LegacyExecuteFlyshotRequest data)
{
_logger.LogInformation(
"ExecuteFlyshot 调用: name={Name}, method={Method}, move_to_start={MoveToStart}, use_cache={UseCache}, wait={Wait}",
data.name, data.method, data.move_to_start, data.use_cache, data.wait);
try
{
_compatService.ExecuteTrajectoryByName(
data.name,
new FlyshotExecutionOptions(
moveToStart: data.move_to_start,
method: data.method,
saveTrajectory: data.save_traj,
useCache: data.use_cache,
wait: data.wait));
_logger.LogInformation("ExecuteFlyshot 成功: name={Name}", data.name);
return Ok(new { status = "FlyShot executed", success = true });
}
catch (Exception exception)
{
_logger.LogError(exception, "ExecuteFlyshot 失败: name={Name}", data.name);
return StatusCode(StatusCodes.Status500InternalServerError, new { detail = exception.Message });
}
}
/// <summary>
/// 兼容旧 `SaveTrajInfo(name, method)` 参数形状。
/// </summary>
/// <param name="request">轨迹保存请求体。</param>
/// <returns>旧 FastAPI 层风格的状态响应。</returns>
[HttpPost("/save_traj_info/")]
public IActionResult SaveTrajectoryInfo([FromBody] LegacyTrajectoryInfoRequest request)
{
_logger.LogInformation("SaveTrajectoryInfo 调用: name={Name}, method={Method}", request.name, request.method);
try
{
_compatService.SaveTrajectoryInfo(request.name, request.method);
_logger.LogInformation("SaveTrajectoryInfo 成功: name={Name}", request.name);
return Ok(new { status = "trajectory info saved", success = true });
}
catch (NotSupportedException exception)
{
_logger.LogWarning(exception, "SaveTrajectoryInfo 不支持: name={Name}", request.name);
return StatusCode(StatusCodes.Status501NotImplemented, new { detail = exception.Message });
}
catch (Exception exception)
{
_logger.LogError(exception, "SaveTrajectoryInfo 失败: name={Name}", request.name);
return LegacyBadRequest("SaveTrajInfo failed");
}
}
/// <summary>
/// 兼容旧 `IsFlyShotTrajValid(time, name, method, save_traj)` 参数形状。
/// </summary>
/// <param name="request">轨迹有效性检查请求体。</param>
/// <returns>有效性和轨迹时长。</returns>
[HttpPost("/is_flyShotTrajValid/")]
public IActionResult IsFlyshotTrajectoryValid([FromBody] LegacyFlyshotValidationRequest request)
{
_logger.LogInformation("IsFlyshotTrajectoryValid 调用: name={Name}, method={Method}", request.name, request.method);
try
{
var isValid = _compatService.IsFlyshotTrajectoryValid(
out var duration,
request.name,
request.method,
request.save_traj);
_logger.LogInformation("IsFlyshotTrajectoryValid 结果: name={Name}, valid={Valid}, duration={Duration}s", request.name, isValid, duration.TotalSeconds);
return Ok(new { success = isValid, valid = isValid, time = duration.TotalSeconds });
}
catch (NotSupportedException exception)
{
_logger.LogWarning(exception, "IsFlyshotTrajectoryValid 不支持: name={Name}", request.name);
return StatusCode(StatusCodes.Status501NotImplemented, new { detail = exception.Message });
}
catch (Exception exception)
{
_logger.LogError(exception, "IsFlyshotTrajectoryValid 失败: name={Name}", request.name);
return LegacyBadRequest("IsFlyShotTrajValid failed");
}
}
/// <summary>
/// 兼容旧 `/set_speedRatio/` 路由。
/// </summary>
/// <param name="data">速度倍率请求体。</param>
/// <returns>旧 FastAPI 层风格的状态响应。</returns>
[HttpPost("/set_speedRatio/")]
public IActionResult SetSpeedRatio([FromBody] LegacySpeedRatioRequest data)
{
_logger.LogInformation("SetSpeedRatio 调用: speed={Speed}", data.speed);
try
{
// 验证数值 范围符合预期(例如 0.01到 1.0),以避免对控制器造成潜在风险
if (data.speed < 0.01 || data.speed > 1.0)
{
_logger.LogWarning("SetSpeedRatio 参数无效: speed={Speed}", data.speed);
return BadRequest(new { detail = "Speed ratio must be between 0.01 and 1.0." });
}
_compatService.SetSpeedRatio(data.speed);
_logger.LogInformation("SetSpeedRatio 成功: speed={Speed}", data.speed);
return Ok(new { message = "set_speedRatio executed", returnCode = 0 });
}
catch (Exception exception)
{
_logger.LogError(exception, "SetSpeedRatio 失败: speed={Speed}", data.speed);
return LegacyBadRequest("set_speedRatio failed");
}
}
/// <summary>
/// 兼容旧 `/delete_flyshot/` 路由。
/// </summary>
/// <param name="request">包含轨迹名称的请求体。</param>
/// <returns>旧 FastAPI 层风格的状态响应。</returns>
[HttpPost("/delete_flyshot/")]
public IActionResult DeleteFlyshot([FromBody] LegacyNameRequest request)
{
_logger.LogInformation("DeleteFlyshot 调用: name={Name}", request.name);
try
{
_compatService.DeleteTrajectory(request.name);
_logger.LogInformation("DeleteFlyshot 成功: name={Name}", request.name);
return Ok(new { status = "FlyShot deleted" });
}
catch (Exception exception)
{
_logger.LogError(exception, "DeleteFlyshot 失败: name={Name}", request.name);
return LegacyBadRequest("DeleteFlyShotTraj failed");
}
}
/// <summary>
/// 兼容旧 `/init_mpc_robt` 路由,保留历史拼写。
/// </summary>
/// <param name="data">初始化请求体。</param>
/// <returns>旧 FastAPI 层风格的初始化结果。</returns>
[HttpPost("/init_mpc_robt")]
public IActionResult InitMpcRobot([FromBody] LegacyInitMpcRobotRequest data)
{
_logger.LogInformation(
"InitMpcRobot 调用: robot_name={RobotName}, robot_ip={RobotIp}, sim={Sim}, server={ServerIp}:{Port}",
data.robot_name, data.robot_ip, data.sim, data.server_ip, data.port);
try
{
_compatService.SetUpRobot(data.robot_name);
if (!_compatService.IsSetUp)
{
_logger.LogWarning("InitMpcRobot 失败: Robot not setup");
return LegacyBadRequest("Robot not setup");
}
_compatService.SetActiveController(data.sim);
_compatService.Connect(data.robot_ip);
_compatService.EnableRobot(8);
_logger.LogInformation("InitMpcRobot 成功: robot_name={RobotName}", data.robot_name);
return Ok(new { message = "init_Success", returnCode = 0 });
}
catch (Exception exception)
{
_logger.LogError(exception, "InitMpcRobot 失败");
return LegacyBadRequest("Connect Server failed");
}
}
/// <summary>
/// 兼容旧 `/get_pose` 路由。
/// </summary>
/// <returns>当前末端位姿数组。</returns>
[HttpGet("/get_pose")]
public IActionResult GetPose()
{
try
{
return Ok(new { pose = _compatService.GetPose() });
}
catch
{
return LegacyBadRequest("GetPose failed");
}
}
/// <summary>
/// 解析旧 `/execute_trajectory/` 的完整参数形状。
/// </summary>
/// <param name="payload">原始 JSON 请求体。</param>
/// <param name="queryMethod">查询字符串中的 method 覆盖值。</param>
/// <param name="querySaveTrajectory">查询字符串中的 save_traj 覆盖值。</param>
/// <returns>统一后的路点和执行参数。</returns>
private static (
IReadOnlyList<IReadOnlyList<double>> 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);
}
/// <summary>
/// 解析旧 `/execute_trajectory/` 可能出现的两种历史请求体形状。
/// </summary>
/// <param name="waypoints">原始 JSON 请求体。</param>
/// <returns>统一后的关节路点集合。</returns>
private static IReadOnlyList<IReadOnlyList<double>> ParseLegacyTrajectoryWaypoints(JsonElement waypoints)
{
if (waypoints.ValueKind != JsonValueKind.Array)
{
throw new InvalidOperationException("ExecuteTrajectory request body must be an array.");
}
var parsedWaypoints = new List<IReadOnlyList<double>>();
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;
}
/// <summary>
/// 构造与旧 FastAPI `HTTPException(status_code=400, detail=...)` 等价的响应。
/// </summary>
/// <param name="detail">错误详情文本。</param>
/// <returns>400 JSON 响应。</returns>
private BadRequestObjectResult LegacyBadRequest(string detail)
{
return BadRequest(new { detail });
}
/// <summary>
/// 构造与旧 FastAPI `422` 输入校验失败等价的响应。
/// </summary>
/// <param name="detail">错误详情文本。</param>
/// <returns>422 JSON 响应。</returns>
private ObjectResult LegacyValidationError(string detail)
{
return StatusCode(StatusCodes.Status422UnprocessableEntity, new { detail });
}
}
/// <summary>
/// 表示旧 `/set_tcp/` 路由使用的三维 TCP 请求体。
/// </summary>
public sealed class LegacyTcpRequest
{
/// <summary>
/// 获取或设置 TCP X。
/// </summary>
public double x { get; init; }
/// <summary>
/// 获取或设置 TCP Y。
/// </summary>
public double y { get; init; }
/// <summary>
/// 获取或设置 TCP Z。
/// </summary>
public double z { get; init; }
}
/// <summary>
/// 表示旧 `/move_joint/` 路由使用的关节请求体。
/// </summary>
public sealed class LegacyJointPositionRequest
{
/// <summary>
/// 获取或设置目标关节数组。
/// </summary>
public List<double> joints { get; init; } = [];
}
/// <summary>
/// 表示 `/move_pose/` 路由使用的直角位姿请求体。
/// </summary>
public sealed class LegacyCartesianPoseRequest
{
/// <summary>
/// MovePose 第一版入口硬限制TCP X/Y 最大绝对值,单位 mm。
/// </summary>
private const double MaxHorizontalMillimeters = 1000.0;
/// <summary>
/// MovePose 第一版入口硬限制TCP Z 最小值,单位 mm。
/// </summary>
private const double MinZMillimeters = 0.0;
/// <summary>
/// MovePose 第一版入口硬限制TCP Z 最大值,单位 mm。
/// </summary>
private const double MaxZMillimeters = 1200.0;
/// <summary>
/// MovePose 第一版入口硬限制W/R 姿态角最大绝对值,单位 deg。
/// </summary>
private const double MaxRollYawDegrees = 180.0;
/// <summary>
/// MovePose 第一版入口硬限制P 姿态角最大绝对值,单位 deg。
/// </summary>
private const double MaxPitchDegrees = 90.0;
/// <summary>
/// 获取或设置 TCP X单位为 mm。
/// </summary>
public double x { get; init; }
/// <summary>
/// 获取或设置 TCP Y单位为 mm。
/// </summary>
public double y { get; init; }
/// <summary>
/// 获取或设置 TCP Z单位为 mm。
/// </summary>
public double z { get; init; }
/// <summary>
/// 获取或设置姿态 W单位为 deg。
/// </summary>
public double w { get; init; }
/// <summary>
/// 获取或设置姿态 P单位为 deg。
/// </summary>
public double p { get; init; }
/// <summary>
/// 获取或设置姿态 R单位为 deg。
/// </summary>
public double r { get; init; }
/// <summary>
/// 从原始 JSON 请求体解析直角位姿并显式拒绝缺字段、null 和非有限数。
/// </summary>
/// <param name="json">原始 JSON 请求体。</param>
/// <returns>解析后的直角位姿请求。</returns>
public static LegacyCartesianPoseRequest FromJson(JsonElement json)
{
if (json.ValueKind != JsonValueKind.Object)
{
throw new ArgumentException("MovePose request body must be an object.");
}
// 旧接口要求请求体必须完整提供 x/y/z/w/p/r不能让模型绑定把缺字段静默补 0。
return new LegacyCartesianPoseRequest
{
x = ReadRequiredFiniteDouble(json, "x", -MaxHorizontalMillimeters, MaxHorizontalMillimeters),
y = ReadRequiredFiniteDouble(json, "y", -MaxHorizontalMillimeters, MaxHorizontalMillimeters),
z = ReadRequiredFiniteDouble(json, "z", MinZMillimeters, MaxZMillimeters),
w = ReadRequiredFiniteDouble(json, "w", -MaxRollYawDegrees, MaxRollYawDegrees),
p = ReadRequiredFiniteDouble(json, "p", -MaxPitchDegrees, MaxPitchDegrees),
r = ReadRequiredFiniteDouble(json, "r", -MaxRollYawDegrees, MaxRollYawDegrees)
};
}
/// <summary>
/// 转换为兼容层使用的六维位姿数组。
/// </summary>
/// <returns>[x,y,z,w,p,r] 数组。</returns>
public IReadOnlyList<double> ToPoseArray()
{
return [x, y, z, w, p, r];
}
/// <summary>
/// 读取必填有限数值字段。
/// </summary>
/// <param name="json">请求体 JSON 对象。</param>
/// <param name="propertyName">字段名。</param>
/// <param name="minInclusive">允许的最小值。</param>
/// <param name="maxInclusive">允许的最大值。</param>
/// <returns>字段对应的有限 double 数值。</returns>
private static double ReadRequiredFiniteDouble(
JsonElement json,
string propertyName,
double minInclusive,
double maxInclusive)
{
if (!json.TryGetProperty(propertyName, out var property) || property.ValueKind != JsonValueKind.Number)
{
throw new ArgumentException($"MovePose request field '{propertyName}' is required and must be a number.");
}
var value = property.GetDouble();
if (double.IsNaN(value) || double.IsInfinity(value))
{
throw new ArgumentOutOfRangeException(propertyName, "MovePose request values must be finite.");
}
if (value < minInclusive || value > maxInclusive)
{
throw new ArgumentOutOfRangeException(
propertyName,
value,
$"MovePose request field '{propertyName}' must be between {minInclusive} and {maxInclusive}.");
}
return value;
}
}
/// <summary>
/// 表示旧 `/upload_flyshot/` 路由使用的飞拍上传请求体。
/// </summary>
public sealed class LegacyFlightTrajectoryRequest
{
/// <summary>
/// 获取或设置地址组集合。
/// </summary>
public List<List<int>> addrs { get; init; } = [];
/// <summary>
/// 获取或设置飞拍轨迹名称。
/// </summary>
public string name { get; init; } = string.Empty;
/// <summary>
/// 获取或设置偏移周期集合。
/// </summary>
public List<double> offset_values { get; init; } = [];
/// <summary>
/// 获取或设置拍照标志集合。
/// </summary>
public List<bool> shot_flags { get; init; } = [];
/// <summary>
/// 获取或设置关节路点集合。
/// </summary>
public List<List<double>> waypoints { get; init; } = [];
}
/// <summary>
/// 表示旧 `/execute_flyshot/` 与 `/delete_flyshot/` 路由使用的名称请求体。
/// </summary>
public sealed class LegacyNameRequest
{
/// <summary>
/// 获取或设置轨迹名称。
/// </summary>
public string name { get; init; } = string.Empty;
}
/// <summary>
/// 表示旧 `/execute_flyshot/` 路由使用的完整执行请求体。
/// </summary>
public sealed class LegacyExecuteFlyshotRequest
{
/// <summary>
/// 获取或设置轨迹名称。
/// </summary>
public string name { get; init; } = string.Empty;
/// <summary>
/// 获取或设置是否先移动到轨迹起点。
/// </summary>
public bool move_to_start { get; init; } = true;
/// <summary>
/// 获取或设置轨迹生成方法。
/// </summary>
public string method { get; init; } = "icsp";
/// <summary>
/// 获取或设置是否保存轨迹信息。
/// </summary>
public bool save_traj { get; init; } = true;
/// <summary>
/// 获取或设置是否复用轨迹缓存。
/// </summary>
public bool use_cache { get; init; } = true;
/// <summary>
/// 获取或设置是否等待机器人执行完整条飞拍轨迹后再返回。
/// </summary>
public bool wait { get; init; } = true;
}
/// <summary>
/// 表示旧 `SaveTrajInfo` 参数形状。
/// </summary>
public sealed class LegacyTrajectoryInfoRequest
{
/// <summary>
/// 获取或设置轨迹名称。
/// </summary>
public string name { get; init; } = string.Empty;
/// <summary>
/// 获取或设置轨迹生成方法。
/// </summary>
public string method { get; init; } = "icsp";
}
/// <summary>
/// 表示旧 `IsFlyShotTrajValid` 参数形状。
/// </summary>
public sealed class LegacyFlyshotValidationRequest
{
/// <summary>
/// 获取或设置轨迹名称。
/// </summary>
public string name { get; init; } = string.Empty;
/// <summary>
/// 获取或设置轨迹生成方法。
/// </summary>
public string method { get; init; } = "icsp";
/// <summary>
/// 获取或设置是否保存轨迹信息。
/// </summary>
public bool save_traj { get; init; } = true;
}
/// <summary>
/// 表示旧 `GetNearestIK` 参数形状。
/// </summary>
public sealed class LegacyNearestIkRequest
{
/// <summary>
/// 获取或设置目标位姿 `[x,y,z,qx,qy,qz,qw]`。
/// </summary>
public List<double> pose { get; init; } = [];
/// <summary>
/// 获取或设置 IK seed 关节数组。
/// </summary>
public List<double> seed { get; init; } = [];
}
/// <summary>
/// 表示旧 `/set_speedRatio/` 路由使用的速度倍率请求体。
/// </summary>
public sealed class LegacySpeedRatioRequest
{
/// <summary>
/// 获取或设置目标速度倍率。
/// </summary>
public double speed { get; init; }
}
/// <summary>
/// 表示旧 `/init_mpc_robt` 路由使用的初始化请求体。
/// </summary>
public sealed class LegacyInitMpcRobotRequest
{
/// <summary>
/// 获取或设置目标服务端 IP。
/// </summary>
public string server_ip { get; init; } = string.Empty;
/// <summary>
/// 获取或设置目标服务端端口。
/// </summary>
public int port { get; init; }
/// <summary>
/// 获取或设置机器人名称。
/// </summary>
public string robot_name { get; init; } = string.Empty;
/// <summary>
/// 获取或设置机器人控制器 IP。
/// </summary>
public string robot_ip { get; init; } = string.Empty;
/// <summary>
/// 获取或设置是否使用仿真控制器;默认 false 连接真机。
/// </summary>
public bool sim { get; init; }
}