# 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`。
