Files
FlyShotHost/docs/python-client-interface-reverse-engineering.md
yunxiao.zhu af65ca03a0 feat(compat): 补齐飞拍执行等待与 FANUC 状态驱动链路
- 为 ExecuteFlyShotTraj 补齐 wait 语义,并让 move_to_start
  先完成临时 PTP 运动后再启动正式飞拍轨迹
- 将 J519 命令发送改为由机器人 UDP status sequence 驱动,
  避免在未收到状态包时主动发周期命令
- 将 10010 状态通道关节字段统一按 JointRadians 命名,
  同步更新运行时读取逻辑与协议测试
- 新增 FANUC 10010 状态帧、流运动手册和 Python client
  逆向文档,并更新 README 与兼容需求说明
- 补充兼容层编排测试与 HTTP 集成测试,覆盖 wait 和
  move_to_start 串行化行为
2026-05-03 19:29:31 +08:00

203 lines
8.7 KiB
Markdown
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.

# Python ControllerClient 接口逆向记录
## 背景
本记录用于确认旧 `PyControllerClient` 对 Python 暴露了哪些接口,尤其确认是否能通过 Python client 直接查询或设置旧服务端运行态 `JointLimits`
复核对象:
```text
../flyshot-uaes-interface/lib/PyControllerClient.cpython-37m-x86_64-linux-gnu.so
../FlyingShot/FlyingShot/Lib/PyControllerClient.cpython-37m-x86_64-linux-gnu.so
../flyshot-uaes-interface/lib/libControllerClient.so
../FlyingShot/FlyingShot/Lib/libControllerClient.so
../FlyingShot/FlyingShot/Include/ControllerClient/ControllerClient.h
../FlyingShot/FlyingShot/Include/ControllerClient/Types.h
../flyshot-uaes-interface/UseControllerClient.py
../flyshot-uaes-interface/main.py
```
两份 Python 扩展与两份底层 client 库哈希一致:
```text
PyControllerClient.cpython-37m-x86_64-linux-gnu.so
SHA256=648CC23CBC6DF83822B58AC4A10211EE1DF8029AD8933D31032187748DF7F4BC
libControllerClient.so
SHA256=6D6FD3F20F0791F1CF11EEE5B1D479E2DCB6A1A2C8AB00A1165575BAB4B62813
```
因此 `flyshot-uaes-interface/lib``FlyingShot/FlyingShot/Lib` 中的 Python client 可视为同一份接口。
## 暴露的 Python 类型
`PyControllerClient` 暴露以下类型:
| 类型 | 来源 | 说明 |
| --- | --- | --- |
| `ControllerClient` | `ControllerClient.h` | TCP JSON client高层控制入口 |
| `JointPositions` | `Types.h` | 关节位置容器,可用 6 维列表构造,也支持下标读写 |
| `Pose` | `Types.h` | TCP/末端位姿容器C++ 侧为 7 元数组 |
| `JointLimits` | `Types.h` | 关节上下限、速度、加速度、jerk 容器 |
| `IOType` | `Types.h` | IO 枚举 |
`IOType` 的枚举值:
```text
IOType.kIOTypeDI = 1
IOType.kIOTypeDO = 2
IOType.kIOTypeRI = 8
IOType.kIOTypeRO = 9
```
## ControllerClient 暴露方法
二进制字符串和 C++ 公开头文件交叉确认Python client 暴露的方法为:
| Python 方法 | 典型调用 | 返回形态 | 说明 |
| --- | --- | --- | --- |
| `ConnectServer` | `c.ConnectServer(server_ip="127.0.0.1", port=50001)` | `bool` | 连接旧 `50001/TCP+JSON` 服务端 |
| `GetServerVersion` | `c.GetServerVersion()` | `str` | Python 包装层把 C++ out 参数折叠成返回值 |
| `GetClientVersion` | `c.GetClientVersion()` | `str` | 获取 client 版本 |
| `SetUpRobot` | `c.SetUpRobot("FANUC_LR_Mate_200iD")` | `bool` | 按机器人名称初始化服务端机器人模型 |
| `SetUpRobotFromEnv` | `c.SetUpRobotFromEnv(env_file)` | `bool` | 从环境文件初始化 |
| `IsSetUp` | `c.IsSetUp()` | `bool` | 判断服务端是否已经初始化机器人 |
| `SetShowTCP` | `c.SetShowTCP(is_show=True, axis_length=0.1, axis_size=2)` | `bool` | 仿真显示 TCP 坐标系 |
| `GetName` | `c.GetName()` | `str` | 获取机器人名称 |
| `GetDoF` | `c.GetDoF()` | `int` | 获取自由度 |
| `SetActiveController` | `c.SetActiveController(sim=True)` | `bool` | 切换仿真/真实控制器 |
| `Connect` | `c.Connect("192.168.10.101")` | `bool` | 连接机器人控制器 |
| `Disconnect` | `c.Disconnect()` | `bool` | 断开机器人控制器 |
| `EnableRobot` | `c.EnableRobot()` / `c.EnableRobot(8)` | `bool` | 使能机器人,参数为 buffer size |
| `DisableRobot` | `c.DisableRobot()` | `bool` | 下使能 |
| `GetSpeedRatio` | `c.GetSpeedRatio()` | `float` | 获取执行速度倍率 |
| `SetSpeedRatio` | `c.SetSpeedRatio(0.8)` | `bool` | 设置执行速度倍率 |
| `GetTCP` | `res, tcp = c.GetTCP()` | `(bool, Pose)` | 获取 TCP |
| `SetTCP` | `c.SetTCP(tcp)` | `bool` | 设置 TCP |
| `GetIO` | `res, value = c.GetIO(port=1, io_type=IOType.kIOTypeDI)` | `(bool, bool)` | 读取 IO |
| `SetIO` | `c.SetIO(port=1, value=True, io_type=IOType.kIOTypeDO)` | `bool` | 写 IO |
| `StopMove` | `c.StopMove()` | `bool` | 停止运动 |
| `GetJointPosition` | `res, joints = c.GetJointPosition()` | `(bool, JointPositions)` | 获取当前关节角 |
| `GetPose` | `res, pose = c.GetPose()` | `(bool, Pose)` | 获取当前末端位姿 |
| `GetNearestIK` | `res, ik = c.GetNearestIK(pose, joint_seed=joints)` | `(bool, JointPositions)` | 按 seed 求最近 IK |
| `MoveJoint` | `c.MoveJoint(joint_positions)` | `bool` | 关节运动 |
| `ExecuteTrajectory` | `c.ExecuteTrajectory(waypoints=[...], method="icsp", save_traj=True)` | `bool` | 执行普通轨迹 |
| `UploadFlyShotTraj` | `c.UploadFlyShotTraj(name, waypoints, shot_flags, offset_values, addrs)` | `bool` | 上传飞拍轨迹 |
| `DeleteFlyShotTraj` | `c.DeleteFlyShotTraj(name)` | `bool` | 删除飞拍轨迹 |
| `ListFlyShotTraj` | `c.ListFlyShotTraj()` | `list[str]` | 列出已上传飞拍轨迹 |
| `ExecuteFlyShotTraj` | `c.ExecuteFlyShotTraj(name, move_to_start=True, method="icsp", save_traj=True)` | `bool` | 执行飞拍轨迹 |
| `SaveTrajInfo` | `c.SaveTrajInfo(name, method="icsp")` | `bool` | 保存规划结果到 `~/Rvbust/Data` |
| `IsFlyShotTrajValid` | `valid, time = c.IsFlyShotTrajValid(name, method="icsp", save_traj=True)` | `(bool, float)` | 检查飞拍轨迹是否合法并返回规划时长 |
## 没有暴露的关键接口
本轮重点确认Python client 暴露方法中没有看到:
```text
GetJointLimits
SetJointLimits
_GetJointLimits
_SetJointLimits
```
虽然 `PyControllerClient` 绑定了 `JointLimits` 类型,并且 `libControllerClient.so` 中存在 `JointLimits` 的输出运算符符号,但公开 `ControllerClient` 方法表中没有任何接收或返回 `JointLimits` 的 client 入口。
这和旧服务端二进制不同。旧服务端 `ControllerServer.cpython-37m-x86_64-linux-gnu.so` 中能看到:
```text
ControllerServer.ControllerServer._GetJointLimits
ControllerServer.ControllerServer._SetJointLimits
ControllerServer.ControllerServer._IsWaypointInJointLimits
ControllerServer.ControllerServer._IsTrajInJointLimits
ControllerServer.ControllerServer._IsTrajInJerkLimits
```
因此当前判断是:
```text
Python client 公开 API 不能直接抓 runtime JointLimits
runtime JointLimits 查询能力存在于旧服务端内部,而不是 PyControllerClient 公开接口中。
```
## UAES Python 服务实际使用的接口
`../flyshot-uaes-interface/main.py` 只使用了公开 client 方法:
- `ConnectServer`
- `SetUpRobot`
- `IsSetUp`
- `EnableRobot`
- `DisableRobot`
- `SetActiveController`
- `Connect`
- `GetName`
- `GetServerVersion`
- `GetDoF`
- `GetSpeedRatio`
- `SetTCP`
- `GetTCP`
- `SetIO`
- `GetJointPosition`
- `MoveJoint`
- `ListFlyShotTraj`
- `UploadFlyShotTraj`
- `ExecuteFlyShotTraj`
- `SetSpeedRatio`
- `DeleteFlyShotTraj`
- `GetPose`
其中 `/execute_flyshot/` 调用:
```text
c.ExecuteFlyShotTraj(name=name, move_to_start=True, method="icsp", save_traj=True)
```
`/set_speedRatio/` 调用:
```text
c.SetSpeedRatio(speed)
```
没有看到 UAES 服务通过 Python client 设置或查询 `JointLimits`
2026-04-30 追加 `50001/TCP+JSON` 抓包复核后,这个判断进一步收敛。`all-50001-plan.pcap` 中已经抓到两次真实规划/执行请求:
```json
{"cmd":"SetSpeedRatio","ratio":0.5}
{"cmd":"ExecuteFlyShotTraj","method":"icsp","move_to_start":true,"name":"UTTC_MS11_TEST01","save_traj":true,"use_cache":false,"wait":true}
{"cmd":"SetSpeedRatio","ratio":1.0}
{"cmd":"ExecuteFlyShotTraj","method":"icsp","move_to_start":true,"name":"UTTC_MS11_TEST01","save_traj":true,"use_cache":false,"wait":true}
```
请求中仍没有 `JointLimits / acc_limit / jerk_limit / velocity / acceleration / jerk`。因此公开 Python client 与公开 50001 JSON 都没有把规划限制作为参数传给 `ExecuteFlyShotTraj`
另外,`main.py``/execute_trajectory/` 中出现:
```text
c.yrxm(waypoints=joint_positions, method='icsp', save_traj=True)
```
`yrxm` 不在 `PyControllerClient` 暴露方法表中,按上下文应是 `ExecuteTrajectory` 的笔误;这条不影响飞拍主路径 `/execute_flyshot/`
## 对当前时长差异调查的含义
如果要抓旧系统规划时使用的 effective `vel/acc/jerk`,优先级应调整为:
1. 在旧服务端进程内直接调用或插桩 `_GetJointLimits`
2. 或者逆向 `50001/TCP+JSON` 的 hidden command envelope再尝试发送 `GetJointLimits` / `_GetJointLimits`
3. 不应指望现有 `PyControllerClient.ControllerClient` 直接提供 `GetJointLimits`
如果短期内无法进入旧服务端内部,新系统不再继续等待这份不可见状态;设计上使用 replacement-only 的内部规划约束参数补齐,优先限制规划加速度,例如 `planning_acceleration_scale`。该参数必须标注为新系统校准值,不能写成旧 Python client 或旧 50001 JSON 的公开字段。
最小现场验证脚本可以先确认 Python client 暴露面:
```python
from PyControllerClient import ControllerClient
c = ControllerClient()
names = [x for x in dir(c) if "Limit" in x or "limit" in x]
print(names)
```
按当前二进制逆向,预期不会出现 `GetJointLimits` / `SetJointLimits`