feat(fanuc): 添加协议编解码与状态页" -m "* 固化 10010 状态帧、10012 命令帧和 60015 J519 包编解码

* 扩展 ControllerClient 兼容层的执行参数和运行时编排
  * 新增 /status 页面与 /api/status/snapshot 状态快照接口
  * 补充 FANUC 协议、客户端和状态接口的最小验证测试
  * 更新 README、兼容要求和真机 Socket 通信实现计划
This commit is contained in:
2026-04-24 21:26:25 +08:00
parent 8a20d9f507
commit a78e6761cb
25 changed files with 3773 additions and 55 deletions

View File

@@ -51,6 +51,26 @@ public sealed class LegacyHttpApiController : ControllerBase
}
}
/// <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>
@@ -70,6 +90,49 @@ public sealed class LegacyHttpApiController : ControllerBase
}
}
/// <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>
@@ -81,15 +144,16 @@ public sealed class LegacyHttpApiController : ControllerBase
}
/// <summary>
/// 兼容旧 `/enable_robot/` 路由;保持原 Python 服务固定传 `8` 的行为
/// 兼容旧 `EnableRobot(buffer_size=2)` 参数形状
/// </summary>
/// <param name="buffer_size">控制器执行缓冲区大小。</param>
/// <returns>旧 FastAPI 层风格的布尔状态响应。</returns>
[HttpGet("/enable_robot/")]
public IActionResult EnableRobot()
public IActionResult EnableRobot([FromQuery] int buffer_size = 2)
{
try
{
_compatService.EnableRobot(8);
_compatService.EnableRobot(buffer_size);
return Ok(new { enable_robot = true });
}
catch
@@ -116,6 +180,24 @@ public sealed class LegacyHttpApiController : ControllerBase
}
}
/// <summary>
/// 提供与旧客户端 <c>StopMove</c> 语义对应的 HTTP 端点。
/// </summary>
/// <returns>旧 FastAPI 层风格的状态响应。</returns>
[HttpGet("/stop_move/")]
public IActionResult StopMove()
{
try
{
_compatService.StopMove();
return Ok(new { status = "move stopped" });
}
catch
{
return LegacyBadRequest("StopMove failed");
}
}
/// <summary>
/// 兼容旧 `/set_active_controller/` 路由。
/// </summary>
@@ -154,6 +236,24 @@ public sealed class LegacyHttpApiController : ControllerBase
}
}
/// <summary>
/// 提供与旧客户端 <c>Disconnect</c> 语义对应的 HTTP 端点。
/// </summary>
/// <returns>旧 FastAPI 层风格的状态响应。</returns>
[HttpPost("/disconnect_robot/")]
public IActionResult DisconnectRobot()
{
try
{
_compatService.Disconnect();
return Ok(new { status = "robot disconnected" });
}
catch
{
return LegacyBadRequest("Disconnect failed");
}
}
/// <summary>
/// 兼容旧 `/robot_info/` 路由。
/// </summary>
@@ -234,6 +334,25 @@ public sealed class LegacyHttpApiController : ControllerBase
}
}
/// <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)
{
try
{
return Ok(new { value = _compatService.GetIo(port, io_type) });
}
catch
{
return LegacyBadRequest("GetDigitalOutput failed");
}
}
/// <summary>
/// 兼容旧 `/get_joint_position/` 路由。
/// </summary>
@@ -270,6 +389,28 @@ public sealed class LegacyHttpApiController : ControllerBase
}
}
/// <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>
@@ -292,11 +433,17 @@ public sealed class LegacyHttpApiController : ControllerBase
/// <param name="waypoints">轨迹请求体。</param>
/// <returns>旧 FastAPI 层风格的状态响应。</returns>
[HttpPost("/execute_trajectory/")]
public IActionResult ExecuteTrajectory([FromBody] JsonElement waypoints)
public IActionResult ExecuteTrajectory(
[FromBody] JsonElement waypoints,
[FromQuery] string? method = null,
[FromQuery] bool? save_traj = null)
{
try
{
_compatService.ExecuteTrajectory(ParseLegacyTrajectoryWaypoints(waypoints));
var request = ParseExecuteTrajectoryRequest(waypoints, method, save_traj);
_compatService.ExecuteTrajectory(
request.Waypoints,
new TrajectoryExecutionOptions(request.Method, request.SaveTrajectory));
return Ok(new { status = "trajectory executed" });
}
catch
@@ -349,14 +496,20 @@ public sealed class LegacyHttpApiController : ControllerBase
/// <summary>
/// 兼容旧 `/execute_flyshot/` 路由。
/// </summary>
/// <param name="data">包含轨迹名称的请求体。</param>
/// <param name="data">包含轨迹名称和执行参数的请求体。</param>
/// <returns>旧 FastAPI 层风格的状态响应。</returns>
[HttpPost("/execute_flyshot/")]
public IActionResult ExecuteFlyshot([FromBody] LegacyNameRequest data)
public IActionResult ExecuteFlyshot([FromBody] LegacyExecuteFlyshotRequest data)
{
try
{
_compatService.ExecuteTrajectoryByName(data.name);
_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)
@@ -365,6 +518,57 @@ public sealed class LegacyHttpApiController : ControllerBase
}
}
/// <summary>
/// 兼容旧 `SaveTrajInfo(name, method)` 参数形状。
/// </summary>
/// <param name="request">轨迹保存请求体。</param>
/// <returns>旧 FastAPI 层风格的状态响应。</returns>
[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");
}
}
/// <summary>
/// 兼容旧 `IsFlyShotTrajValid(time, name, method, save_traj)` 参数形状。
/// </summary>
/// <param name="request">轨迹有效性检查请求体。</param>
/// <returns>有效性和轨迹时长。</returns>
[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");
}
}
/// <summary>
/// 兼容旧 `/set_speedRatio/` 路由。
/// </summary>
@@ -420,7 +624,7 @@ public sealed class LegacyHttpApiController : ControllerBase
return LegacyBadRequest("Robot not setup");
}
_compatService.SetActiveController(sim: false);
_compatService.SetActiveController(data.sim);
_compatService.Connect(data.robot_ip);
_compatService.EnableRobot(2);
return Ok(new { message = "init_Success", returnCode = 0 });
@@ -448,6 +652,47 @@ public sealed class LegacyHttpApiController : ControllerBase
}
}
/// <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>
@@ -576,6 +821,90 @@ public sealed class LegacyNameRequest
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>
/// 表示旧 `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>
@@ -611,4 +940,9 @@ public sealed class LegacyInitMpcRobotRequest
/// 获取或设置机器人控制器 IP。
/// </summary>
public string robot_ip { get; init; } = string.Empty;
/// <summary>
/// 获取或设置是否使用仿真控制器;默认 false 连接真机。
/// </summary>
public bool sim { get; init; }
}